Skip to content

mycontroller.py #702

@xiatongchen

Description

@xiatongchen

#!/usr/bin/env python3

SPDX-License-Identifier: Apache-2.0

import argparse
import os
import sys
from time import sleep
import datetime
import json

import grpc

Import P4Runtime lib from parent utils dir

sys.path.append(
os.path.join(os.path.dirname(os.path.abspath(file)),
'../../utils/'))
import p4runtime_lib.bmv2
import p4runtime_lib.helper
from p4runtime_lib.error_utils import printGrpcError
from p4runtime_lib.switch import ShutdownAllSwitchConnections

SWITCH_TO_HOST_PORT = 1
SWITCH_TO_SWITCH_PORT_1 = 2
SWITCH_TO_SWITCH_PORT_2 = 3
SWITCH_TO_SWITCH_PORT_3 = 3

COUNTER_FILES = {
's1_ingress': 'logs/s1_ingress_counters.json',
's1_egress': 'logs/s1_egress_counters.json',
's2_ingress': 'logs/s2_ingress_counters.json',
's2_egress': 'logs/s2_egress_counters.json',
's3_ingress': 'logs/s3_ingress_counters.json',
's3_egress': 'logs/s3_egress_counters.json'
}

def ensure_log_directory():
"""确保日志目录存在"""
os.makedirs('logs', exist_ok=True)

def write_counter_to_file(counter_data, filename):
"""将计数器数据写入文件"""
try:
with open(filename, 'w') as f:
json.dump(counter_data, f, indent=2)
except Exception as e:
print(f"Error writing counter data to {filename}: {e}")

def writeTunnelRules(p4info_helper, ingress_sw, egress_sw, tunnel_id,
dst_eth_addr, dst_ip_addr, transit_port):
"""
安装完整的隧道规则
"""

table_entry = p4info_helper.buildTableEntry(
    table_name="MyIngress.ipv4_lpm",
    match_fields={
        "hdr.ipv4.dstAddr": (dst_ip_addr, 32)
    },
    action_name="MyIngress.myTunnel_ingress",
    action_params={
        "dst_id": tunnel_id,
    })
ingress_sw.WriteTableEntry(table_entry)
print(f"Installed ingress tunnel rule on {ingress_sw.name} for {dst_ip_addr} -> tunnel {tunnel_id}")

table_entry = p4info_helper.buildTableEntry(
    table_name="MyIngress.myTunnel_exact",
    match_fields={
        "hdr.myTunnel.dst_id": tunnel_id
    },
    action_name="MyIngress.myTunnel_forward",
    action_params={
        "port": transit_port
    })
ingress_sw.WriteTableEntry(table_entry)
print(f"Installed transit tunnel rule on {ingress_sw.name} for tunnel {tunnel_id} -> port {transit_port}")

table_entry = p4info_helper.buildTableEntry(
    table_name="MyIngress.myTunnel_exact",
    match_fields={
        "hdr.myTunnel.dst_id": tunnel_id
    },
    action_name="MyIngress.myTunnel_egress",
    action_params={
        "dstAddr": dst_eth_addr,
        "port": SWITCH_TO_HOST_PORT
    })
egress_sw.WriteTableEntry(table_entry)
print(f"Installed egress tunnel rule on {egress_sw.name} for tunnel {tunnel_id} -> {dst_eth_addr}")

def writeDirectRoutingRules(p4info_helper, sw, host_ip, host_mac, port):
"""
安装直接路由规则,用于非隧道通信
"""
table_entry = p4info_helper.buildTableEntry(
table_name="MyIngress.ipv4_lpm",
match_fields={
"hdr.ipv4.dstAddr": (host_ip, 32)
},
action_name="MyIngress.ipv4_forward",
action_params={
"dstAddr": host_mac,
"port": port
})
sw.WriteTableEntry(table_entry)
print(f"Installed direct routing rule on {sw.name} for {host_ip} -> port {port}")

def readTableRules(p4info_helper, sw):
"""
读取交换机上的所有表规则
"""
print(f'\n----- Reading tables rules for {sw.name} -----')
try:
for response in sw.ReadTableEntries():
for entity in response.entities:
entry = entity.table_entry
table_name = p4info_helper.get_tables_name(entry.table_id)
print(f"Table: {table_name}")
print(f"Match: {entry.match}")
print(f"Action: {entry.action}")
print('-----')
except Exception as e:
print(f"Error reading table rules from {sw.name}: {e}")

def readTunnelCounter(p4info_helper, sw, counter_name, index):
"""
读取指定计数器的值
"""
try:
counter_id = p4info_helper.get_counters_id(counter_name)
for response in sw.ReadCounters(counter_id, index):
for entity in response.entities:
counter = entity.counter_entry
return counter.data.packet_count, counter.data.byte_count
except Exception as e:
print(f"Error reading counter {counter_name} from {sw.name}: {e}")
return 0, 0

def printTunnelCounters(p4info_helper, s1, s2, s3):
"""
按照指定格式打印隧道计数器
"""
print("\nReading tunnel counters ---")

s1_ingress_packets, s1_ingress_bytes = readTunnelCounter(p4info_helper, s1, "MyIngress.ingressTunnelCounter", 100)
s2_egress_packets, s2_egress_bytes = readTunnelCounter(p4info_helper, s2, "MyIngress.egressTunnelCounter", 100)
print(f"--- s1 -> s2 ---")
print(f"s1 MyIngress.ingressTunnelCounter 100: {s1_ingress_packets} packets ({s1_ingress_bytes} bytes)")
print(f"s2 MyIngress.egressTunnelCounter 100: {s2_egress_packets} packets ({s2_egress_bytes} bytes)")
print()

s2_ingress_packets, s2_ingress_bytes = readTunnelCounter(p4info_helper, s2, "MyIngress.ingressTunnelCounter", 101)
s1_egress_packets, s1_egress_bytes = readTunnelCounter(p4info_helper, s1, "MyIngress.egressTunnelCounter", 101)
print(f"--- s2 -> s1 ---")
print(f"s2 MyIngress.ingressTunnelCounter 101: {s2_ingress_packets} packets ({s2_ingress_bytes} bytes)")
print(f"s1 MyIngress.egressTunnelCounter 101: {s1_egress_packets} packets ({s1_egress_bytes} bytes)")
print()

s1_ingress_packets, s1_ingress_bytes = readTunnelCounter(p4info_helper, s1, "MyIngress.ingressTunnelCounter", 200)
s3_egress_packets, s3_egress_bytes = readTunnelCounter(p4info_helper, s3, "MyIngress.egressTunnelCounter", 200)
print(f"--- s1 -> s3 ---")
print(f"s1 MyIngress.ingressTunnelCounter 200: {s1_ingress_packets} packets ({s1_ingress_bytes} bytes)")
print(f"s3 MyIngress.egressTunnelCounter 200: {s3_egress_packets} packets ({s3_egress_bytes} bytes)")
print()

s3_ingress_packets, s3_ingress_bytes = readTunnelCounter(p4info_helper, s3, "MyIngress.ingressTunnelCounter", 201)
s1_egress_packets, s1_egress_bytes = readTunnelCounter(p4info_helper, s1, "MyIngress.egressTunnelCounter", 201)
print(f"--- s3 -> s1 ---")
print(f"s3 MyIngress.ingressTunnelCounter 201: {s3_ingress_packets} packets ({s3_ingress_bytes} bytes)")
print(f"s1 MyIngress.egressTunnelCounter 201: {s1_egress_packets} packets ({s1_egress_bytes} bytes)")
print()

s2_ingress_packets, s2_ingress_bytes = readTunnelCounter(p4info_helper, s2, "MyIngress.ingressTunnelCounter", 300)
s3_egress_packets, s3_egress_bytes = readTunnelCounter(p4info_helper, s3, "MyIngress.egressTunnelCounter", 300)
print(f"--- s2 -> s3 ---")
print(f"s2 MyIngress.ingressTunnelCounter 300: {s2_ingress_packets} packets ({s2_ingress_bytes} bytes)")
print(f"s3 MyIngress.egressTunnelCounter 300: {s3_egress_packets} packets ({s3_egress_bytes} bytes)")
print()

s3_ingress_packets, s3_ingress_bytes = readTunnelCounter(p4info_helper, s3, "MyIngress.ingressTunnelCounter", 301)
s2_egress_packets, s2_egress_bytes = readTunnelCounter(p4info_helper, s2, "MyIngress.egressTunnelCounter", 301)
print(f"--- s3 -> s2 ---")
print(f"s3 MyIngress.ingressTunnelCounter 301: {s3_ingress_packets} packets ({s3_ingress_bytes} bytes)")
print(f"s2 MyIngress.egressTunnelCounter 301: {s2_egress_packets} packets ({s2_egress_bytes} bytes)")

def install_complete_routing(p4info_helper, s1, s2, s3):
"""
安装完整的路由规则,确保所有主机互通
"""
print("\n" + "="*60)
print("INSTALLING COMPLETE ROUTING RULES")
print("="*60)

print("\n=== Installing Direct Routing Rules ===")

writeDirectRoutingRules(p4info_helper, s1, "10.0.1.1", "08:00:00:00:01:11", 1)  # h1
writeDirectRoutingRules(p4info_helper, s1, "10.0.2.2", "08:00:00:00:02:22", 2)  # h2 via s2
writeDirectRoutingRules(p4info_helper, s1, "10.0.3.3", "08:00:00:00:03:33", 3)  # h3 via s3

writeDirectRoutingRules(p4info_helper, s2, "10.0.2.2", "08:00:00:00:02:22", 1)  # h2
writeDirectRoutingRules(p4info_helper, s2, "10.0.1.1", "08:00:00:00:01:11", 2)  # h1 via s1
writeDirectRoutingRules(p4info_helper, s2, "10.0.3.3", "08:00:00:00:03:33", 3)  # h3 via s3

writeDirectRoutingRules(p4info_helper, s3, "10.0.3.3", "08:00:00:00:03:33", 1)  # h3
writeDirectRoutingRules(p4info_helper, s3, "10.0.1.1", "08:00:00:00:01:11", 2)  # h1 via s1
writeDirectRoutingRules(p4info_helper, s3, "10.0.2.2", "08:00:00:00:02:22", 3)  # h2 via s2

print("\n=== Installing Tunnel Rules ===")

writeTunnelRules(p4info_helper, s1, s2, 100, "08:00:00:00:02:22", "10.0.2.2", 2)
writeTunnelRules(p4info_helper, s2, s1, 101, "08:00:00:00:01:11", "10.0.1.1", 2)

writeTunnelRules(p4info_helper, s1, s3, 200, "08:00:00:00:03:33", "10.0.3.3", 3)
writeTunnelRules(p4info_helper, s3, s1, 201, "08:00:00:00:01:11", "10.0.1.1", 2)

writeTunnelRules(p4info_helper, s2, s3, 300, "08:00:00:00:03:33", "10.0.3.3", 3)
writeTunnelRules(p4info_helper, s3, s2, 301, "08:00:00:00:02:22", "10.0.2.2", 3)

print("\n✓ All routing rules installed successfully!")
print("  You should now be able to pingall and get 6/6")

def install_rules_gradually(p4info_helper, s1, s2, s3):
"""
逐步安装路由规则,模拟网络建立过程
"""
print("\n" + "="*60)
print("STARTING GRADUAL RULE INSTALLATION")
print("="*60)

print("\n=== Phase 1: Direct host connections only ===")
print("Expected pingall result: 0/6 (no inter-switch connectivity)")
sleep(3)

writeDirectRoutingRules(p4info_helper, s1, "10.0.1.1", "08:00:00:00:01:11", 1)
writeDirectRoutingRules(p4info_helper, s2, "10.0.2.2", "08:00:00:00:02:22", 1)
writeDirectRoutingRules(p4info_helper, s3, "10.0.3.3", "08:00:00:00:03:33", 1)

print("\n✓ Phase 1 complete - Only direct host connections installed")
print("  You can now test: mininet> pingall (should show 0/6)")
print("  Waiting 10 seconds for testing...")
sleep(10)


print("\n=== Phase 4: Adding s2-s3 connectivity ===")
print("Expected pingall result: 6/6 (full connectivity)")
sleep(3)

writeDirectRoutingRules(p4info_helper, s2, "10.0.3.3", "08:00:00:00:03:33", 3)
writeDirectRoutingRules(p4info_helper, s3, "10.0.2.2", "08:00:00:00:02:22", 3)

print("\n✓ Phase 4 complete - Full network connectivity achieved!")
print("  You can now test: mininet> pingall (should show 6/6)")
print("  All rules installed successfully!")
print("\n" + "="*60)
print("GRADUAL INSTALLATION COMPLETE")
print("="*60)

def readAllCounters(p4info_helper, sw):
"""
读取交换机的所有计数器并返回结构化数据
"""
counter_data = {
'timestamp': datetime.datetime.now().isoformat(),
'switch': sw.name,
'counters': {}
}

try:

    for response in sw.ReadCounters(p4info_helper.get_counters_id("MyIngress.ingressTunnelCounter"), 0):
        for entity in response.entities:
            counter = entity.counter_entry
            counter_name = f"ingress_tunnel_{counter.index}"
            counter_data['counters'][counter_name] = {
                'packets': counter.data.packet_count,
                'bytes': counter.data.byte_count,
                'index': counter.index
            }

    for response in sw.ReadCounters(p4info_helper.get_counters_id("MyIngress.egressTunnelCounter"), 0):
        for entity in response.entities:
            counter = entity.counter_entry
            counter_name = f"egress_tunnel_{counter.index}"
            counter_data['counters'][counter_name] = {
                'packets': counter.data.packet_count,
                'bytes': counter.data.byte_count,
                'index': counter.index
            }
            
except Exception as e:
    print(f"Error reading counters from {sw.name}: {e}")

return counter_data

def printAndSaveCounters(p4info_helper, s1, s2, s3):
"""
读取所有计数器并打印,同时保存到文件
"""
print('\n----- Reading all counters -----')

s1_counters = readAllCounters(p4info_helper, s1)
s2_counters = readAllCounters(p4info_helper, s2)
s3_counters = readAllCounters(p4info_helper, s3)

print(f"\n--- {s1.name} Counters ---")
for counter_name, counter_data in s1_counters['counters'].items():
    print(f"{counter_name}: {counter_data['packets']} packets, {counter_data['bytes']} bytes")

print(f"\n--- {s2.name} Counters ---")
for counter_name, counter_data in s2_counters['counters'].items():
    print(f"{counter_name}: {counter_data['packets']} packets, {counter_data['bytes']} bytes")
    
print(f"\n--- {s3.name} Counters ---")
for counter_name, counter_data in s3_counters['counters'].items():
    print(f"{counter_name}: {counter_data['packets']} packets, {counter_data['bytes']} bytes")

write_counter_to_file(s1_counters, COUNTER_FILES['s1_ingress'])
write_counter_to_file(s2_counters, COUNTER_FILES['s2_ingress'])
write_counter_to_file(s3_counters, COUNTER_FILES['s3_ingress'])

print("Counter data saved to log files")

def main(p4info_file_path, bmv2_file_path):

ensure_log_directory()

p4info_helper = p4runtime_lib.helper.P4InfoHelper(p4info_file_path)

try:

    s1 = p4runtime_lib.bmv2.Bmv2SwitchConnection(
        name='s1',
        address='127.0.0.1:50051',
        device_id=0,
        proto_dump_file='logs/s1-p4runtime-requests.txt')
    s2 = p4runtime_lib.bmv2.Bmv2SwitchConnection(
        name='s2',
        address='127.0.0.1:50052',
        device_id=1,
        proto_dump_file='logs/s2-p4runtime-requests.txt')
    s3 = p4runtime_lib.bmv2.Bmv2SwitchConnection(
        name='s3',
        address='127.0.0.1:50053',
        device_id=2,
        proto_dump_file='logs/s3-p4runtime-requests.txt')

    s1.MasterArbitrationUpdate()
    s2.MasterArbitrationUpdate()
    s3.MasterArbitrationUpdate()

    s1.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
                                   bmv2_json_file_path=bmv2_file_path)
    print("Installed P4 Program using SetForwardingPipelineConfig on s1")
    s2.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
                                   bmv2_json_file_path=bmv2_file_path)
    print("Installed P4 Program using SetForwardingPipelineConfig on s2")
    s3.SetForwardingPipelineConfig(p4info=p4info_helper.p4info,
                                   bmv2_json_file_path=bmv2_file_path)
    print("Installed P4 Program using SetForwardingPipelineConfig on s3")

    print("\nSelect installation mode:")
    print("1. Complete installation (immediate 6/6 connectivity)")
    print("2. Gradual installation (0/6 -> 2/6 -> 4/6 -> 6/6)")
    
    choice = input("Enter choice (1 or 2, default 1): ").strip()
    
    if choice == "2":
        install_rules_gradually(p4info_helper, s1, s2, s3)
    else:
        install_complete_routing(p4info_helper, s1, s2, s3)

    print("\n----- Reading final table rules -----")
    readTableRules(p4info_helper, s1)
    readTableRules(p4info_helper, s2)
    readTableRules(p4info_helper, s3)

    # 持续监控计数器
    print("\n----- Starting counter monitoring -----")
    counter = 0
    while True:
        sleep(5)
        counter += 1
        print(f'\n----- Counter reading #{counter} -----')
        printTunnelCounters(p4info_helper, s1, s2, s3)
        
        if counter % 5 == 0:
            print("\n----- Periodic table rules check -----")
            readTableRules(p4info_helper, s1)
            readTableRules(p4info_helper, s2)
            readTableRules(p4info_helper, s3)

except KeyboardInterrupt:
    print("\nFininshed ---")

    printTunnelCounters(p4info_helper, s1, s2, s3)
except grpc.RpcError as e:
    printGrpcError(e)

ShutdownAllSwitchConnections()

if name == 'main':
parser = argparse.ArgumentParser(description='P4Runtime Controller')
parser.add_argument('--p4info', help='p4info proto in text format from p4c',
type=str, action="store", required=False,
default='./build/advanced_tunnel.p4.p4info.txt')
parser.add_argument('--bmv2-json', help='BMv2 JSON file from p4c',
type=str, action="store", required=False,
default='./build/advanced_tunnel.json')
args = parser.parse_args()

if not os.path.exists(args.p4info):
    parser.print_help()
    print("\np4info file not found: %s\nHave you run 'make'?" % args.p4info)
    parser.exit(1)
if not os.path.exists(args.bmv2_json):
    parser.print_help()
    print("\nBMv2 JSON file not found: %s\nHave you run 'make'?" % args.bmv2_json)
    parser.exit(1)
main(args.p4info, args.bmv2_json)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions