• P4的exercises实现原理


    今天介绍一下p4的tutorials-master\utils如何将tutorials-master\exercises下各个小实验的实现,有助于以后自己构建mininet并配置p4交换机,通过本博客可以简单了解:

    • Makefile是如何实现相关的实验的
    • 基于p4的mininet如何搭建

    1.Makefile干了什么?

    1.1 实验文件夹下的makefile

    我们这里以防火墙为例,当在实验文件夹下的命令行使用run的时候,真正make的文件其实是工具文件夹下的Makefile
    1. BMV2_SWITCH_EXE = simple_switch_grpc
    2. TOPO = pod-topo/topology.json
    3. DEFAULT_PROG = basic.p4
    4. include ../../utils/Makefile

     1.2 build创建文件夹

    在run的时候,可以看到,先是build了一下,而在build中,创建了一个名称为build文件夹,以供未来编译p4程序使用
    1. run: build
    2. sudo python3 $(RUN_SCRIPT) -t $(TOPO) $(run_args)
    3. build: dirs $(compiled_json)
    4. dirs:
    5. mkdir -p $(BUILD_DIR) $(PCAP_DIR) $(LOG_DIR)

     1.3 编译p4文件成信息文件与json

    build结束以后,进入了p4编译阶段,将make工作目录下的所有p4程序编译一下,生成的信息文件以及json文件都存在了build文件夹下,其中P4C_ARGS += --p4runtime-files $(BUILD_DIR)/$(basename $@).p4.p4info.txt
    1. %.json: %.p4
    2. $(P4C) --p4v 16 $(P4C_ARGS) -o $(BUILD_DIR)/$@ $<

    1.4 run 

     从下面的make代码可以看到,其实run调用的是工具文件夹下的run_exercise.py的文件,python运行传递的参数绕来绕去,实际上很简单,这里以防火墙为例,用到的就是basic.p4文件编译的json用到的交换机是simple_switch_grpc,创建的拓扑是pod-topo/topology.json

    run_exercise.py究竟是干嘛的,在后面说。

    1. run: build
    2. sudo python3 $(RUN_SCRIPT) -t $(TOPO) $(run_args)
    3. #run的一些参数
    4. RUN_SCRIPT = ../../utils/run_exercise.py
    5. TOPO = pod-topo/topology.json
    6. ifndef NO_P4
    7. run_args += -j $(DEFAULT_JSON)
    8. endif
    9. # Set BMV2_SWITCH_EXE to override the BMv2 target
    10. ifdef BMV2_SWITCH_EXE
    11. run_args += -b $(BMV2_SWITCH_EXE)
    12. endif
    13. DEFAULT_JSON = $(BUILD_DIR)/$(DEFAULT_PROG:.p4=.json)
    14. DEFAULT_PROG = basic.p4
    15. BMV2_SWITCH_EXE = simple_switch_grpc

    最后,附上一张图可以很直观的看到,makefile做了什么

     2.run_exercise.py是怎么创建的基于p4的mininet网络的?

    我想,如果是要自己玩一些花样了话,这些多少是要了解一些的。。。

    这里继续以防火墙小实验为例,防火墙小实验中,有4台交换机,但是3台交换机的配置是basic.p4有一台是firewall.p4,这个差异化配置是怎么做到的呢?

    2.1 run_exercise.py大致流程

    从下面的代码看出,简单来说,获取一堆参数,然后创建网络,然后运行起来,就这么简单

    1. if __name__ == '__main__':
    2. # from mininet.log import setLogLevel
    3. # setLogLevel("info")
    4. args = get_args()
    5. exercise = ExerciseRunner(args.topo, args.log_dir, args.pcap_dir,
    6. args.switch_json, args.behavioral_exe, args.quiet)
    7. exercise.run_exercise()

    看了一下参数的获取函数get_args(),结合在make run中涉及到的

    sudo python3 ../../utils/run_exercise.py -t pod-topo/topology.json -j build/basic.json -b simple_switch_grpc

    的指令,就是把拓扑的json,和p4编译后的json还有行为模型传进来

    1. def get_args():
    2. cwd = os.getcwd()
    3. default_logs = os.path.join(cwd, 'logs')
    4. default_pcaps = os.path.join(cwd, 'pcaps')
    5. parser = argparse.ArgumentParser()
    6. parser.add_argument('-q', '--quiet', help='Suppress log messages.',
    7. action='store_true', required=False, default=False)
    8. parser.add_argument('-t', '--topo', help='Path to topology json',
    9. type=str, required=False, default='./topology.json')
    10. parser.add_argument('-l', '--log-dir', type=str, required=False, default=default_logs)
    11. parser.add_argument('-p', '--pcap-dir', type=str, required=False, default=default_pcaps)
    12. parser.add_argument('-j', '--switch_json', type=str, required=False)
    13. parser.add_argument('-b', '--behavioral-exe', help='Path to behavioral executable',
    14. type=str, required=False, default='simple_switch')
    15. return parser.parse_args()

    2.2 ExerciseRunner

    先看构造函数,其实就是把上面的拓扑json提取出来存进来

    1. def __init__(self, topo_file, log_dir, pcap_dir,
    2. switch_json, bmv2_exe='simple_switch', quiet=False):
    3. """ Initializes some attributes and reads the topology json. Does not
    4. actually run the exercise. Use run_exercise() for that.
    5. Arguments:
    6. topo_file : string // A json file which describes the exercise's
    7. mininet topology.
    8. log_dir : string // Path to a directory for storing exercise logs
    9. pcap_dir : string // Ditto, but for mininet switch pcap files
    10. switch_json : string // Path to a compiled p4 json for bmv2
    11. bmv2_exe : string // Path to the p4 behavioral binary
    12. quiet : bool // Enable/disable script debug messages
    13. """
    14. self.quiet = quiet
    15. self.logger('Reading topology file.')
    16. with open(topo_file, 'r') as f:
    17. topo = json.load(f)
    18. self.hosts = topo['hosts']
    19. self.switches = topo['switches']
    20. self.links = self.parse_links(topo['links'])
    21. # Ensure all the needed directories exist and are directories
    22. for dir_name in [log_dir, pcap_dir]:
    23. if not os.path.isdir(dir_name):
    24. if os.path.exists(dir_name):
    25. raise Exception("'%s' exists and is not a directory!" % dir_name)
    26. os.mkdir(dir_name)
    27. self.log_dir = log_dir
    28. self.pcap_dir = pcap_dir
    29. self.switch_json = switch_json
    30. self.bmv2_exe = bmv2_exe

    拓扑的json格式,可以看到,每一个主机都会有一个commands确保以后可以通过arp发现一下对方,在交换机中,具有一个runtime_json,这个是运行时的控制面文件,里面存一些table信息,此外,默认情况下,交换机都会采用默认的p4配置,如果有"program"就会用里面的p4编译的json配置

    1. {
    2. "hosts": {
    3. "h1": {"ip": "10.0.1.1/24", "mac": "08:00:00:00:01:11",
    4. "commands":["route add default gw 10.0.1.10 dev eth0",
    5. "arp -i eth0 -s 10.0.1.10 08:00:00:00:01:00"]},
    6. "h2": {"ip": "10.0.2.2/24", "mac": "08:00:00:00:02:22",
    7. "commands":["route add default gw 10.0.2.20 dev eth0",
    8. "arp -i eth0 -s 10.0.2.20 08:00:00:00:02:00"]},
    9. "h3": {"ip": "10.0.3.3/24", "mac": "08:00:00:00:03:33",
    10. "commands":["route add default gw 10.0.3.30 dev eth0",
    11. "arp -i eth0 -s 10.0.3.30 08:00:00:00:03:00"]},
    12. "h4": {"ip": "10.0.4.4/24", "mac": "08:00:00:00:04:44",
    13. "commands":["route add default gw 10.0.4.40 dev eth0",
    14. "arp -i eth0 -s 10.0.4.40 08:00:00:00:04:00"]}
    15. },
    16. "switches": {
    17. "s1": { "runtime_json" : "pod-topo/s1-runtime.json",
    18. "program" : "build/firewall.json" },
    19. "s2": { "runtime_json" : "pod-topo/s2-runtime.json" },
    20. "s3": { "runtime_json" : "pod-topo/s3-runtime.json" },
    21. "s4": { "runtime_json" : "pod-topo/s4-runtime.json" }
    22. },
    23. "links": [
    24. ["h1", "s1-p1"], ["h2", "s1-p2"], ["s1-p3", "s3-p1"], ["s1-p4", "s4-p2"],
    25. ["h3", "s2-p1"], ["h4", "s2-p2"], ["s2-p3", "s4-p1"], ["s2-p4", "s3-p2"]
    26. ]
    27. }

    2.3 create_nework() 

    • 先看一眼大概流程,可以看到,首先定义一下交换机的默认配置,重点就是这个p4编译的json,json_path=self.switch_json,其实具体来说在这里传进来的参数就是在刚才命令行里看到的-j build/basic.json 
    1. def create_network(self):
    2. """ Create the mininet network object, and store it as self.net.
    3. Side effects:
    4. - Mininet topology instance stored as self.topo
    5. - Mininet instance stored as self.net
    6. """
    7. self.logger("Building mininet topology.")
    8. defaultSwitchClass = configureP4Switch(
    9. sw_path=self.bmv2_exe,
    10. json_path=self.switch_json,
    11. log_console=True,
    12. pcap_dump=self.pcap_dir)
    13. self.topo = ExerciseTopo(self.hosts, self.switches, self.links, self.log_dir, self.bmv2_exe, self.pcap_dir)
    14. self.net = Mininet(topo = self.topo,
    15. link = TCLink,
    16. host = P4Host,
    17. switch = defaultSwitchClass,
    18. controller = None)
    •  在上面的代码中,看到创建了一个拓扑用到了ExerciseTopo,里面就是根据链路、交换机、主机进行一个创建,这里主要看交换机,如果交换机的program字段存在,就比如防火墙的配置存在,就会把这个交换机配置成具有防火墙功能的交换机,
    1. for sw, params in switches.items():
    2. if "program" in params:
    3. switchClass = configureP4Switch(
    4. sw_path=bmv2_exe,
    5. json_path=params["program"],
    6. log_console=True,
    7. pcap_dump=pcap_dir)
    8. else:
    9. # add default switch
    10. switchClass = None
    11. self.addSwitch(sw, log_file="%s/%s.log" %(log_dir, sw), cls=switchClass)

    具体的配置函数看一下就好了,这里不深究,因为就算以后自己写一个p4文件,写一个topo.json也不会改写这个函数,除非你想要调整一下next_thrift_port = xxx:

    1. def configureP4Switch(**switch_args):
    2. """ Helper class that is called by mininet to initialize
    3. the virtual P4 switches. The purpose is to ensure each
    4. switch's thrift server is using a unique port.
    5. """
    6. if "sw_path" in switch_args and 'grpc' in switch_args['sw_path']:
    7. # If grpc appears in the BMv2 switch target, we assume will start P4Runtime
    8. class ConfiguredP4RuntimeSwitch(P4RuntimeSwitch):
    9. def __init__(self, *opts, **kwargs):
    10. kwargs.update(switch_args)
    11. P4RuntimeSwitch.__init__(self, *opts, **kwargs)
    12. def describe(self):
    13. print("%s -> gRPC port: %d" % (self.name, self.grpc_port))
    14. return ConfiguredP4RuntimeSwitch
    15. else:
    16. class ConfiguredP4Switch(P4Switch):
    17. next_thrift_port = 9090
    18. def __init__(self, *opts, **kwargs):
    19. global next_thrift_port
    20. kwargs.update(switch_args)
    21. kwargs['thrift_port'] = ConfiguredP4Switch.next_thrift_port
    22. ConfiguredP4Switch.next_thrift_port += 1
    23. P4Switch.__init__(self, *opts, **kwargs)
    24. def describe(self):
    25. print("%s -> Thrift port: %d" % (self.name, self.thrift_port))
    26. return ConfiguredP4Switch
    • 在执行完下面的代码以后,就已经有一个拓扑大概的样子了,连接,主机,交换机及其类型都配置好了(这里只配置好了防火墙的交换机),因为别的交换机program不存在,所以默认的配置还没有配置,从上面的代码段中看到是None,它们的默认配置在mininet中配置的
    self.topo = ExerciseTopo(self.hosts, self.switches, self.links, self.log_dir, self.bmv2_exe, self.pcap_dir)

    去看一眼mininet的源码,就可以很清楚的发现,带着defaultSwitchClass去配置Mininet,就会把还没没有配置的交换机用defaultSwitchClass配置一下,初始化的时候,传defaultSwitchClass的参进去,而且默认build是True

    1. def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
    2. controller=DefaultController, link=Link, intf=Intf,
    3. build=True, xterms=False, cleanup=False, ipBase='10.0.0.0/8',
    4. inNamespace=False,
    5. autoSetMacs=False, autoStaticArp=False, autoPinCpus=False,
    6. listenPort=None, waitConnected=False ):
    7. ...
    8. ...
    9. ...
    10. if topo and build:
    11. self.build()

    所以在初始化最后一行有一个代码执行了build方法,只看拓扑相关的,build函数实际上调用了buildFromTopo( self.topo ),拓扑就是我们早在ExerciseTopo阶段就已经配置好的拓扑了,但是那个时候,我们只配置了防火墙到s1交换机中,仔细看一下buildFromTopo创建交换机的时候,它的addSwitch方法

    1. def build( self ):
    2. "Build mininet."
    3. if self.topo:
    4. self.buildFromTopo( self.topo )
    5. def buildFromTopo( self, topo=None ):
    6. """Build mininet from a topology object
    7. At the end of this function, everything should be connected
    8. and up."""
    9. # Possibly we should clean up here and/or validate
    10. # the topo
    11. if self.cleanup:
    12. pass
    13. info( '*** Creating network\n' )
    14. if not self.controllers and self.controller:
    15. # Add a default controller
    16. info( '*** Adding controller\n' )
    17. classes = self.controller
    18. if not isinstance( classes, list ):
    19. classes = [ classes ]
    20. for i, cls in enumerate( classes ):
    21. # Allow Controller objects because nobody understands partial()
    22. if isinstance( cls, Controller ):
    23. self.addController( cls )
    24. else:
    25. self.addController( 'c%d' % i, cls )
    26. info( '*** Adding hosts:\n' )
    27. for hostName in topo.hosts():
    28. self.addHost( hostName, **topo.nodeInfo( hostName ) )
    29. info( hostName + ' ' )
    30. info( '\n*** Adding switches:\n' )
    31. for switchName in topo.switches():
    32. # A bit ugly: add batch parameter if appropriate
    33. params = topo.nodeInfo( switchName)
    34. cls = params.get( 'cls', self.switch )
    35. if hasattr( cls, 'batchStartup' ):
    36. params.setdefault( 'batch', True )
    37. self.addSwitch( switchName, **params )
    38. info( switchName + ' ' )
    39. info( '\n*** Adding links:\n' )
    40. for srcName, dstName, params in topo.links(
    41. sort=True, withInfo=True ):
    42. self.addLink( **params )
    43. info( '(%s, %s) ' % ( srcName, dstName ) )
    44. info( '\n' )

    方法里面写到,里面说到,如果交换机没有cls,也就是cls是None,那就把传参的配置配给它,这里传参的配置就是basic.p4进行配置以后生成的类

    1. def addSwitch( self, name, cls=None, **params ):
    2. """Add switch.
    3. name: name of switch to add
    4. cls: custom switch class/constructor (optional)
    5. returns: added switch
    6. side effect: increments listenPort ivar ."""
    7. defaults = { 'listenPort': self.listenPort,
    8. 'inNamespace': self.inNamespace }
    9. defaults.update( params )
    10. if not cls:
    11. cls = self.switch
    12. sw = cls( name, **defaults )
    13. if not self.inNamespace and self.listenPort:
    14. self.listenPort += 1
    15. self.switches.append( sw )
    16. self.nameToNode[ name ] = sw
    17. return sw

     小结

    • 通过阅读makefile文件,了解到如何通过make来运行基于p4的mininet小实验
    • 通过阅读run_exercise等源码,了解到以后要如何设置相关的json文件,用来创建mininet,而这个网络创建的代码其实如果不是特定需求了话,也不需要我们去改
  • 相关阅读:
    Thymeleaf页面布局
    Chrome出现STATUS_STACK_BUFFER_OVERRUN解决方法之一
    Kafka consumer rebalance概览及过程
    php将word中的omath转成mathml
    短URL服务的设计以及实现
    Himall商城字符串帮助类获得指定顺序的字符在字符串中的位置索引
    数据增强Mixup原理与代码解读
    从程序员到架构师,如何才能快速成长?
    使用wget命令报错:Issued certificate has expired(颁发的证书已经过期)
    蓝桥杯嵌入式基础模块——定时器的基本作用(新板)STM32G431(HAL库开发)
  • 原文地址:https://blog.csdn.net/a1164520408/article/details/128208679