P4 06-MPLS

P4示例程序-06 MPLS

  • 功能

    MPLS是一种高效且可扩展的数据转发机制,它不再根据IP包头里的IP地址来做路由查找,而是根据给数据包加上的短标签来快速转发。

  • 拓扑结构

拓扑结构
  • 代码

  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
    37
    38
    "topology": {
    "assignment_strategy": "l3",
    "links": [["h1", "s1"],
    ["s1", "s2"],
    ["s1", "s3"],
    ["s2", "s4"],
    ["s3", "s4"],
    ["s4", "s5"],
    ["s4", "s6"],
    ["s5", "s7"],
    ["s6", "s7"],
    ["s7", "h2"],
    ["s7", "h3"]],
    "hosts": {
    "h1": {
    },
    "h2": {
    },
    "h3": {
    }
    },
    "switches": {
    "s1": {
    },
    "s2": {
    },
    "s3": {
    },
    "s4": {
    },
    "s5": {
    },
    "s6": {
    },
    "s7": {
    }
    }
    }
  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
    typedef bit<9>  egressSpec_t; // 定义egress端口位宽
    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 mpls_t {
    bit<20> label; // MPLS标签字段
    bit<3> exp; // 实验字段
    bit<1> s; // 最底层标签标志位
    bit<8> ttl; // MPLS TTL
    }

    header ipv4_t {
    bit<4> version; // IP版本
    bit<4> ihl; // 首部长度
    bit<8> diffserv; // 差异化服务
    bit<16> totalLen; // 总长度
    bit<16> identification; // 标识符
    bit<3> flags; // 标志位
    bit<13> fragOffset; // 分段偏移
    bit<8> ttl; // TTL
    bit<8> protocol; // 上层协议
    bit<16> hdrChecksum; // 首部校验和
    ip4Addr_t srcAddr; // 源IP地址
    ip4Addr_t dstAddr; // 目的IP地址
    }

    struct metadata {
    bit<1> is_ingress_border; // 是否是入边界口
    bit<1> is_egress_border; // 是否是出边界口
    }

    struct headers {
    ethernet_t ethernet; // 以太网头部
    mpls_t mpls; // MPLS头部
    ipv4_t ipv4; // IPv4头部
    }
  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
    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_MPLS: parse_mpls; // MPLS类型则转mpls解析
    TYPE_IPV4: parse_ipv4; // IPv4类型则转ipv4解析
    default: accept; // 其他则直接接受
    }
    }

    state parse_mpls {
    packet.extract(hdr.mpls); // 提取MPLS头部
    transition parse_ipv4; // 继续提取IPv4头
    }

    state parse_ipv4 {
    packet.extract(hdr.ipv4); // 提取IPv4头部
    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    control MyIngress(inout headers hdr,
    inout metadata meta,
    inout standard_metadata_t standard_metadata) {

    action drop() {
    mark_to_drop(standard_metadata); // 标记丢包
    }

    action set_is_ingress_border(){
    meta.is_ingress_border = (bit<1>)1; // 标记为入边界口
    }

    table check_is_ingress_border {
    key = {
    standard_metadata.ingress_port: exact; // 根据入端口判断
    }
    actions = {
    NoAction; // 无操作
    set_is_ingress_border; // 设置入边界标志
    }
    default_action = NoAction; // 默认无操作
    size = CONST_MAX_PORTS; // 表大小为32
    }

    action add_mpls_header(bit<20> tag) {
    hdr.mpls.setValid(); // 设置MPLS头有效
    hdr.mpls.label = tag; // 指定标签
    hdr.mpls.s = 0; // 非最底层
    hdr.mpls.ttl = 255; // 初始TTL
    hdr.ethernet.etherType = TYPE_MPLS; // 修改以太类型为MPLS
    }

    table fec_to_label {
    key = {
    hdr.ipv4.dstAddr: lpm; // 匹配目标IP前缀
    }
    actions = {
    NoAction; // 无操作
    add_mpls_header; // 添加MPLS标签
    }
    default_action = NoAction; // 默认不处理
    size = CONST_MAX_LABELS; // 表大小为10
    }

    action mpls_forward(macAddr_t dstAddr, egressSpec_t port) {
    hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; // 设置源地址为本机
    hdr.ethernet.dstAddr = dstAddr; // 设置目标地址
    standard_metadata.egress_spec = port; // 指定出端口
    hdr.mpls.ttl = hdr.mpls.ttl - 1; // TTL减一
    }

    table mpls_tbl {
    key = {
    hdr.mpls.label: exact; // 匹配MPLS标签
    }
    actions = {
    mpls_forward; // MPLS转发
    drop; // 丢弃
    }
    default_action = drop; // 默认丢弃
    size = CONST_MAX_LABELS; // 表大小为10
    }

    action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
    hdr.ethernet.srcAddr = hdr.ethernet.dstAddr; // 设置源地址为本机
    hdr.ethernet.dstAddr = dstAddr; // 设置目标地址
    standard_metadata.egress_spec = port; // 指定出端口
    hdr.ipv4.ttl = hdr.ipv4.ttl - 1; // TTL减一
    }

    table ipv4_lpm {
    key = {
    hdr.ipv4.dstAddr: lpm; // 匹配目标IP前缀
    }
    actions = {
    ipv4_forward; // IPv4转发
    drop; // 丢弃
    }
    default_action = drop; // 默认丢弃
    size = 128; // 表大小
    }

    apply {

    // We check if it is an ingress border port
    check_is_ingress_border.apply(); // 判断是否入边界口

    if(meta.is_ingress_border == 1){

    // We need to check if the header is valid since mpls label is based on dst ip
    if(hdr.ipv4.isValid()){

    // We add the label based on the destination
    fec_to_label.apply(); // 添加MPLS标签
    }
    }

    // We select the egress port based on the mpls label
    if(hdr.mpls.isValid()){
    mpls_tbl.apply(); // MPLS转发表
    }

    // We implement normal forwarding
    else if (hdr.ipv4.isValid())
    {
    ipv4_lpm.apply(); // 普通IPv4转发
    }

    }
    }
  6. Egress 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
    control MyEgress(inout headers hdr,
    inout metadata meta,
    inout standard_metadata_t standard_metadata) {

    action is_egress_border(){

    // We remove the mpls header
    hdr.mpls.setInvalid(); // 删除MPLS头
    hdr.ethernet.etherType = TYPE_IPV4; // 恢复以太类型
    hdr.ipv4.ttl = hdr.ipv4.ttl - 1; // TTL减一
    }

    table check_is_egress_border {
    key = {
    standard_metadata.egress_port: exact; // 根据出端口判断
    }
    actions = {
    NoAction; // 无操作
    is_egress_border; // 设置为出边界口
    }
    default_action = NoAction; // 默认无操作
    size = CONST_MAX_PORTS; // 表大小为32
    }

    apply {
    // We check if it is an egress border port
    if (hdr.mpls.isValid()){
    check_is_egress_border.apply(); // 判断并处理出边界口
    }
    }
    }
  7. Checksum Computation
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    control MyComputeChecksum(inout headers  hdr, inout metadata meta) {
    apply {
    update_checksum(
    hdr.ipv4.isValid(), // 检查IPv4有效性
    { hdr.ipv4.version,
    hdr.ipv4.ihl,
    hdr.ipv4.diffserv,
    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); // 计算IPv4首部校验和
    }
    }
  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.mpls); // 发送MPLS头部(如果有效)
    packet.emit(hdr.ipv4); // 发送IPv4头部(如果有效)
    }
    }
  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
    # S1交换机
    # 针对发完主机H2的IPv4数据包添加MPLS标签
    table_add FEC_tbl mpls_ingress_4_hop 10.7.2.0/24 => 2 3 2 2
    # 针对发完主机H3的IPv4数据包添加MPLS标签
    table_add FEC_tbl mpls_ingress_4_hop 10.7.3.0/24 => 2 3 2 2
    # MPLS标签指示从端口2转发,将到达交换机S2
    table_add mpls_tbl mpls_forward 2 0 => 00:00:00:02:01:00 2
    # 正常的IPv4数据包转发,从端口1转发进而到达主机H1
    table_add FEC_tbl ipv4_forward 10.1.1.0/24 => 00:00:0a:01:01:02 1
  11. s2-commands.txt
    1
    2
    3
    # S2交换机
    # 剥离顶层MPLS标签,并从端口2转发至下一跳交换机S4
    table_add mpls_tbl mpls_forward 2 0 => 00:00:00:04:01:00 2
  12. s3-commands.txt
    1
    2
    3
    # S3交换机
    # 剥离全部MPLS标签、恢复为IPv4数据包,并从1端口转发至交换机S1
    table_add mpls_tbl penultimate 1 1 => 00:00:00:01:03:00 1
  13. s4-commands.txt
    1
    2
    3
    4
    5
    # S4交换机
    # 剥离顶层MPLS标签,并从端口3转发至下一跳交换机S5
    table_add mpls_tbl mpls_forward 3 0 => 00:00:00:05:01:00 3
    # 剥离顶层MPLS标签,并从端口2转发至下一跳交换机S3
    table_add mpls_tbl mpls_forward 2 0 => 00:00:00:03:02:00 2
  14. s5-commands.txt
    1
    2
    3
    # S5交换机
    # 剥离全部MPLS标签、恢复为IPv4数据包,并从2端口转发至交换机S7
    table_add mpls_tbl penultimate 2 1 => 00:00:00:07:01:00 2
  15. s6-commands.txt
    1
    2
    3
    # S6交换机
    # 剥离顶层MPLS标签,并从端口1转发至下一跳交换机S4
    table_add mpls_tbl mpls_forward 1 0 => 00:00:00:04:04:00 1
  16. s7-commands.txt
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # S7交换机
    # 针对发完主机H1的IPv4数据包添加MPLS标签
    table_add FEC_tbl mpls_ingress_4_hop 10.1.1.0/24 => 1 2 1 2
    # MPLS标签指示从端口2转发,将到达交换机S6
    table_add mpls_tbl mpls_forward 2 0 => 00:00:00:06:02:00 2
    # 正常的IPv4数据包转发,从端口3转发进而到达主机H2
    table_add FEC_tbl ipv4_forward 10.7.2.0/24 => 00:00:0a:07:02:02 3
    # 正常的IPv4数据包转发,从端口4转发进而到达主机H3
    table_add FEC_tbl ipv4_forward 10.7.3.0/24 => 00:00:0a:07:03:02 4
  17. controller.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
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    from p4utils.utils.helper import load_topo
    from p4utils.utils.sswitch_p4runtime_API import SimpleSwitchP4RuntimeAPI
    from p4utils.utils.sswitch_thrift_API import SimpleSwitchThriftAPI

    # 加载 topology.json 拓扑配置文件
    topo = load_topo('topology.json')
    # 用于保存每台交换机的 P4Runtime 控制器对象
    controllers = {}
    # 遍历拓扑中所有 P4Runtime 交换机,初始化对应的控制器对象
    for switch, data in topo.get_p4rtswitches().items():
    controllers[switch] = SimpleSwitchP4RuntimeAPI(
    data['device_id'],
    data['grpc_port'],
    p4rt_path=data['p4rt_path'],
    json_path=data['json_path']
    )

    # -------------------------------
    # 配置 s1:入口边界交换机
    # -------------------------------
    controller = controllers['s1']
    # 设置端口 1 为入口边界端口
    controller.table_add('check_is_ingress_border', 'set_is_ingress_border', ['1'])
    # 配置目的地址为 10.7.2.2/32 时添加 MPLS 标签 2(发往 h2)
    controller.table_add('fec_to_label', 'add_mpls_header', ['10.7.2.2/32'], ['2'])
    # 配置目的地址为 10.7.3.2/32 时添加 MPLS 标签 3(发往 h3)
    controller.table_add('fec_to_label', 'add_mpls_header', ['10.7.3.2/32'], ['3'])
    # 根据标签 2 进行转发,出端口 2,目的 MAC 为下一跳 s2
    controller.table_add('mpls_tbl', 'mpls_forward', ['2'], ['00:00:00:02:01:00', '2'])
    # 根据标签 3 进行转发,出端口 3,目的 MAC 为下一跳 s3
    controller.table_add('mpls_tbl', 'mpls_forward', ['3'], ['00:00:00:03:01:00', '3'])
    # 标签 1 用于回程流量(h2/h3 → h1),出端口 1
    controller.table_add('mpls_tbl', 'mpls_forward', ['1'], ['00:00:0a:01:01:02', '1'])
    # 设置端口 1 为出口边界(处理回程 MPLS 流量)
    controller.table_add('check_is_egress_border', 'is_egress_border', ['1'])

    # -------------------------------
    # 配置 s2:中间 MPLS 节点
    # -------------------------------
    controller = controllers['s2']
    # 标签 2 → 发往 s4,端口 2
    controller.table_add('mpls_tbl', 'mpls_forward', ['2'], ['00:00:00:04:02:00', '2'])
    # 标签 1(回程)→ 发往 s1,端口 1
    controller.table_add('mpls_tbl', 'mpls_forward', ['1'], ['00:00:00:01:02:00', '1'])

    # -------------------------------
    # 配置 s3:中间 MPLS 节点
    # -------------------------------
    controller = controllers['s3']
    # 标签 3 → 发往 s4,端口 2
    controller.table_add('mpls_tbl', 'mpls_forward', ['3'], ['00:00:00:04:03:00', '2'])

    # -------------------------------
    # 配置 s4:汇聚 MPLS 节点
    # -------------------------------
    controller = controllers['s4']
    # 标签 2 → 发往 s6,端口 4
    controller.table_add('mpls_tbl', 'mpls_forward', ['2'], ['00:00:00:06:04:00', '4'])
    # 标签 3 → 发往 s5,端口 3
    controller.table_add('mpls_tbl', 'mpls_forward', ['3'], ['00:00:00:05:04:00', '3'])
    # 标签 1(回程)→ 发往 s2,端口 1
    controller.table_add('mpls_tbl', 'mpls_forward', ['1'], ['00:00:00:02:04:00', '1'])

    # -------------------------------
    # 配置 s5:通往 s7 的一条路径
    # -------------------------------
    controller = controllers['s5']
    # 标签 3 → 发往 s7,端口 2
    controller.table_add('mpls_tbl', 'mpls_forward', ['3'], ['00:00:00:07:05:00', '2'])

    # -------------------------------
    # 配置 s6:另一条路径,最终到达 s7
    # -------------------------------
    controller = controllers['s6']
    # 标签 2 → 发往 s7,端口 2
    controller.table_add('mpls_tbl', 'mpls_forward', ['2'], ['00:00:00:07:06:00', '2'])
    # 标签 1(回程)→ 发往 s4,端口 1
    controller.table_add('mpls_tbl', 'mpls_forward', ['1'], ['00:00:00:04:06:00', '1'])

    # -------------------------------
    # 配置 s7:出口边界交换机(h2、h3)
    # -------------------------------
    controller = controllers['s7']
    # 标签 2 → 发往 h2,端口 3
    controller.table_add('mpls_tbl', 'mpls_forward', ['2'], ['00:00:0a:07:02:02', '3'])
    # 标签 3 → 发往 h3,端口 4
    controller.table_add('mpls_tbl', 'mpls_forward', ['3'], ['00:00:0a:07:03:02', '4'])
    # 设置端口 3 和 4 为 ingress border(回程流量起始点)
    controller.table_add('check_is_ingress_border', 'set_is_ingress_border', ['3'])
    controller.table_add('check_is_ingress_border', 'set_is_ingress_border', ['4'])
    # 设置端口 3 和 4 为出口边界(解封装流量)
    controller.table_add('check_is_egress_border', 'is_egress_border', ['3'])
    controller.table_add('check_is_egress_border', 'is_egress_border', ['4'])
    # 配置回程流量(目的为 h1)的 FEC → 标签 1
    controller.table_add('fec_to_label', 'add_mpls_header', ['10.1.1.2/32'], ['1'])
    # 标签 1(回程)→ 发往 s6,端口 2
    controller.table_add('mpls_tbl', 'mpls_forward', ['1'], ['00:00:00:06:07:00', '2'])

    # 本地 IP 转发规则:不使用 MPLS,直接发往 h2、h3
    controller.table_add('ipv4_lpm', 'ipv4_forward', ['10.7.2.2/32'], ['00:00:0a:07:02:02', '3'])
    controller.table_add('ipv4_lpm', 'ipv4_forward', ['10.7.3.2/32'], ['00:00:0a:07:03:02', '4'])

  18. 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
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    from p4utils.mininetlib.network_API import NetworkAPI

    # 创建一个 NetworkAPI 实例,用于定义网络拓扑
    net = NetworkAPI()
    # 设置网络日志级别为 'info',可输出适量调试信息
    net.setLogLevel('info')
    # 指定编译器使用 P4Runtime 模式
    net.setCompiler(p4rt=True)
    # 执行名为 controller.py 的控制器脚本,并在每次运行时重新启动该脚本
    net.execScript('python controller.py', reboot=True)
    # 添加七个基于 P4Runtime 的交换机,分别命名为 s1 至 s7
    net.addP4RuntimeSwitch('s1')
    net.addP4RuntimeSwitch('s2')
    net.addP4RuntimeSwitch('s3')
    net.addP4RuntimeSwitch('s4')
    net.addP4RuntimeSwitch('s5')
    net.addP4RuntimeSwitch('s6')
    net.addP4RuntimeSwitch('s7')
    # 所有交换机使用名为 basics.p4 的 P4 程序作为数据面逻辑
    net.setP4SourceAll('basics.p4')
    # 添加三台主机 h1、h2、h3
    net.addHost('h1')
    net.addHost('h2')
    net.addHost('h3')
    # 连接主机 h1 到交换机 s1,连接到 s1 的端口 1
    net.addLink("h1", "s1", port2=1)

    # 以下构建的是一个多路径菱形结构的拓扑,支持负载均衡和路径冗余
    # 连接 s1 的端口 2 到 s2 的端口 1
    net.addLink("s1", "s2", port1=2, port2=1)
    # 连接 s1 的端口 3 到 s3 的端口 1
    net.addLink("s1", "s3", port1=3, port2=1)
    # 连接 s2 的端口 2 到 s4 的端口 1
    net.addLink("s2", "s4", port1=2, port2=1)
    # 连接 s3 的端口 2 到 s4 的端口 2
    net.addLink("s3", "s4", port1=2, port2=2)
    # s4 是拓扑的中间节点,连接两个子路径分支:s5 和 s6
    net.addLink("s4", "s5", port1=3, port2=1)
    net.addLink("s4", "s6", port1=4, port2=1)
    # s5 和 s6 都连向汇聚节点 s7
    net.addLink("s5", "s7", port1=2, port2=1)
    net.addLink("s6", "s7", port1=2, port2=2)
    # s7 是终点交换机,连接主机 h2(端口 3)和 h3(端口 4)
    net.addLink("s7", "h2", port1=3)
    net.addLink("s7", "h3", port1=4)

    # 启用三层自动地址分配与默认路由配置
    net.l3()
    # 启用对所有节点的 pcap 抓包功能,便于后续数据包分析
    net.enablePcapDumpAll()
    # 启用所有节点的日志记录
    net.enableLogAll()
    # 启动命令行交互界面 CLI,便于调试与测试网络行为
    net.enableCli()
    # 启动整个网络,包括交换机、主机和控制器
    net.startNetwork()
  • P4仿真

  1. 配置匹配表
配置匹配表
  1. 启动网络拓扑
启动网络拓扑
  1. 启动pingall命令,发现主机间成功通信
pingall命令

P4 06-MPLS
http://example.com/2025/07/31/P4 06-MPLS/
作者
Wsdbybyd
发布于
2025年7月31日
许可协议