3 个版本 (重大更新)

0.6.0 2022年11月9日
0.5.0 2021年7月15日
0.4.0 2018年10月30日

#10 in #control-plane

ISC 许可证

300KB
7K SLoC

Rust 6.5K SLoC // 0.0% comments Python 611 SLoC // 0.1% comments C 107 SLoC // 0.1% comments

pyportus

本模块提供了对Portus CCP实现的Python接口。

设置

这些Python绑定可在PyPI上找到,使用命令pip install pyportus安装。

手动构建时,本项目使用maturin

测试

  1. 创建虚拟环境
  2. maturin develop

安装

  1. maturin build
  2. pip install生成的.whl文件

然后运行/测试,执行命令cd examples && ./<venv>/python3 aimd.py(可能需要以root身份运行)

编写算法

概述

Portus中的算法由一个Python类表示,该类的实例表示单个TCP流。对于每个流创建一个新实例。

此类必须是portus.AlgBase的子类,并必须实现以下两个方法签名:

  • on_create(self)
  • on_report(self,r)
    • r 是一个包含您数据路径程序中定义的所有字段以及当前 CwndRate 的报告对象。假设您的程序只定义了一个变量:(def (acked 0)),其中 acked 是自上次报告以来累加的总字节数。这个值可以通过 r.acked 来访问。同样,您可以像访问 cwnd 或 rate 一样访问它,即 r.Cwndr.Rate(注意大小写重要)。

类的每个实例都会自动在 self 中包含两个字段

  • self.datapath 是指向数据路径对象的指针,可以用来安装新的数据路径程序。它有两个可用方法
    1. datapath.install( str ),它接受一个字符串作为数据路径程序。它会编译程序并将其安装到数据路径中。它不会返回任何内容,但如果您的程序无法编译,可能会引发异常。
    2. datapath.update_field(field, val),它接受数据路径程序 Report 范围中的一个变量,并将其值设置为 val。例如,要仅更新 cwnd,您可以使用 datapath.update_field("Cwnd", 10000)(注意:cwnd 以字节为单位表示,而不是数据包)。
  • self.datapath_info 是一个结构体,包含有关数据路径中此特定流字段的详细信息(例如,在 on_create 中,可以根据数据路径的 mss 设置初始 cwnd)
    • sock_id:此流在数据路径中的唯一标识符
    • init_cwnd:此流在您设置之前将具有的初始拥塞窗口
    • src_ipsrc_portdst_ipdst_port:流的数据源和目的地的 IP 地址和端口

数据路径程序

数据路径程序用于(1)定义要发送回您的用户空间程序的统计信息以及发送的频率,以及(2)设置拥塞窗口和/或速率。数据路径程序是用一个非常简单的类似于 Lisp 的方言编写的,由一个变量定义行后跟任意数量的 when 子句组成

(def ( ... ) ( ... ))
(when (event) (
  do_stuff ...
)
(when (other_event) (
    do_other_stuff ...
)

注意:以下信息已过时,因为数据路径程序 API 已更新

更新

1. 报告变量定义

示例:(def (Report.acked 0) (Report.rtt 0) (Report.timeout false))

此行定义了报告范围内的变量名称及其初始值。在数据路径程序中调用 (report) 会导致调用算法的 on_report 函数,并传递这些变量的当前值。 调用后,这些变量将重置为其初始值。

注意:数据路径程序中的变量以 {scope}.{name} 的形式书写。例如,在 Report 范围内的 acked 变量被写成 Report.acked。因此,此行定义的所有变量都必须Report. 开头。然而,当你在 on_report 中访问它们时,只需提供变量名。在我们的示例中,Report.rttReport 范围内定义了变量 rtt。如果我们想在 on_report(r) 中访问此值,我们将使用 r.rtt(即 不是 r.Report.rtt)。

2. 当子句

当子句由一个布尔表达式和一组指令组成。在每次确认后,数据路径会检查布尔表达式,如果它评估为 true,则运行指令集。例如,以下当子句会在每个 rtt 后发送报告(即调用 on_report 函数)。

(when (> Micros Flow.rtt_sample_us)
    (report)
)

综合起来

一个示例算法定义,显示完整的 API

import portus

# Class must sublcass portus.AlgBase
class SampleCCAlg(portus.AlgBase):
  # Init must take exactly these parameters
  def __init__(self, datapath, datapath_info):
    # Store a copy of the datapath and info for later
    self.datapath = datapath
    self.datapath_info = datapath_info
    
    # Internally store an initial cwnd value
    self.cwnd = 10 * self.datapath_info.mss
    
    # Install an initial datapath program to keep track of the RTT and report it once per RTT
    # The first when clause is true on every single ack,
    #    which means the 'Report.rtt' field will always keep the latest rtt sample
    # The second when clause is true once one rtt's worth of time has passed, 
    #    at which point it will trigger on_report, and Micros (and Report.rtt) will be reset to 0
    self.datapath.install("""\
    (def
        (Report.rtt 0)
    )
    (when true
        (:= Report.rtt Flow.rtt_sample_us)
        (fallthrough)
    )
    (when (> Micros Flow.rtt_sample_us)
        (report)
    )
    """)

  # This function will be called once per RTT, and the report struct `r` will contain:
  # "rtt", "Cwnd", and "Rate"
  def on_report(self, r):
      # Compute new cwnd internally 
      # If the rtt has decreased, increase the cwnd by 1 packet, else decrease by 1 packet
      if self.last_rtt < r.rtt:
          self.cwnd += self.datapath_info.mss
      else:
          self.cwnd -= self.datapath_info.mss
      self.last_rtt = r.rtt
      
      # Send this new value of cwnd to the datapath
      self.datapath.update_field("Cwnd", self.cwnd)
    

重要提示

  1. 您应该在您的 __init__ 实现中安装一个初始数据路径程序,否则您将不会收到任何报告,也不会发生任何事情。您可以在处理 on_report 时稍后安装不同的数据路径程序。
  2. 如果您想打印任何内容,应使用 sys.stderr.write()(注意您需要 import sys,并且它不会像 print 那样自动添加新行)。
  3. 必须self 中存储对 datapath 的引用,名为 "datapath"(即 self.datapath = datapath),因为库内部使用它来访问数据路径结构。

启动 CCP

CCP 的入口点是 portus.connect(ipc_type, class, debug, blocking)

  • ipc_type (string):在 Linux 上为(netlink | unix | char),在 macOS 上为(unix)
  • class:您的算法类,例如 SampleCCAlg
  • debug (bool):如果为 true,则 CCP 将记录 ccp 和数据路径之间传递的所有消息
  • blocking (bool):如果为 true,则使用阻塞 ipc 读取,否则使用非阻塞

例如: portus.connect("netlink", SampleCCAlg, debug=True, blocking=True)

无论您使用阻塞或非阻塞套接字,connect都将永久阻塞(要停止CCP,请发送ctrl+c或终止进程)。

示例

有关定义算法和运行CCP的完整示例,请参阅./aimd.py中的简单AIMD方案,并尝试运行它: sudo python aimd.py

依赖项

~10–16MB
~218K SLoC