标签: SDN

  • OpenFlow协议学习

    OpenFlow 1.0 协议总结

    OpenFlow交换机中包含了1个以上的流表,流表又包含多个流表项。对于接收到的数据包,OpenFlow交换机在流表中选择一个最合适的流表项,并按流表项的内容对数据包进行处理。

    流表中包含的流表项由以下3个基本要素组成:Head Field, Counter, Action。Head Field即匹配规则,Counter即匹配次数,Action即匹配后所需要采取的操作。

    流表项

    Head Field

    • Ingress Port
    • Ethernet source address
    • Ethernet destination address
    • Ethernet type
    • VLAN id
    • VLAN Priority(802.1q PCP)
    • IP source address
    • IP destination address
    • ToS
    • Transport source port/ICMP type
    • Transport destination port/ICMP code

    在1.0版本中,STP(802.1d)数据包不执行流表匹配。完成STP处理后,执行“头字段解析之后”,会在此基础上进行数据包与流表的匹配。当数据包与刘表内设置的多条流表项匹配时,优先级最高的流表项即为匹配结果。OpenFlow中可以存在多个流表,但必须从流表0开始匹配。若编号为0的流表中不存在与数据包相匹配的流表项,而在其他流表中存在的话,则前进到下一流表中执行匹配。 数据包与OpenFlow交换机中任何一个流表项都不匹配的情况称之为Table-miss,这种情况下会利用Packet-In消息将数据包转发至控制器,或者丢弃。具体情况会根据设置决定。

    Counter

    4种计数器 Per Table, Per Port, Per Flow, Per Queue。

    Per Table: Active Entries, Packet Lookups, Packet Matches

    Per Flow: Received Packets/Bytes, Duration(msec/usec)

    Per Queue: Transmit Packets/Bytes, Transmit Overrun Errors

    Per Port: Received Packets/Bytes/Drops/Errors, Transmit Packets/Bytes/Drops/Errors, Receive Frame Alignment Errors, Received Overrun Errors, Received CRC Errors, Collisions

    Action

    Forward, Drop, Enqueue(Optional), Modify-Field(Optional)

    Forward

    名称 说明 虚拟端口
    (port) 转发至指定端口 port
    ALL 除接收端口以外的所有端口 0xfffc
    CONTROLLER 控制器 0xfffd
    LOCAL 发送至本地网络栈 0xfffe
    TABLE 执行流表中行动(仅Packet-Out) 0xfff9
    IN_PORT 从输入端口发出 0xfff8
    NORMAL(optional) 传统L2或L3交换机动作 0xfffa
    FLOOD(optional) 按照STP发送 0xfffb

    Drop

    未在协议中明确说明

    Enqueue

    在等待队列的末尾添加数据包

    Modify-Field

    • 设置VLAN ID
    • 设置VLAN priority
    • 去掉VLAN头
    • 修改源MAC
    • 修改目的MAC
    • 修改源IP
    • 修改目的IP
    • 修改ToS
    • 修改源端口
    • 修改目的端口

    控制器和交换机之间的安全通道

    安全通道通过控制面网络来建立,不受OpenFlow交换机中的流表项的影响。规范中安全通道应该使用TLS实现,但在OpenFlow 1.1开始添加了TCP明文的方式,默认的TCP端口为6653。

    struct openflow_header {
        uint8_t version; // 版本,OpenFlow 1.0-1.3分别对应0x01-0x04
        uint8_t type; // 消息类型,表明OpenFlow消息的类型
        uint16_t length; // Byte数
        uint32_t xid; // 事务ID
    }
    

    安全通道的建立过程

    1. 建立TCP/TLS连接
    2. 确定使用的OpenFlow版本,type字段值为OFPT_HELLO,在version中放入各自支持的最大版本号。
    3. 握手,type字段值为OFPT_FEATURES_REQUEST和OFPT_FEATURES_RESPONSE
    struct features_response {
        uint64_t datapath_id; // datapath id唯一标识OpenFlow交换机
        uint32_t n_buffer; // 缓存数据包的最大个数
        uint8_t n_tables; // 支持的流表个数
        char pad[24]; 
        uint32_t capabilities; // 支持的容量
        uint32_t actions; // 支持的行动
        struct ofp_phy_port ports[??]; // 物理端口信息
    }
    
    struct ofp_phy_port {
        uint16_t port_no; // 物理端口号
        uint8_t hw_addr[OFP_ETH_LENGTH]; // 48位的以太网地址
        uint8_t name[OFP_MAX_PORT_NAME_LEN]; // 128位的端口名称
        uint32_t config; // 端口设置bitmap
        uint32_t state; // 端口状态bitmap
        uint32_t curr; // 当前功能
        uint32_t advertised; // 广播功能
        uint32_t supported;  // 支持功能
        uint32_t peer;  // 连接方广播功能
    }
    
    1. 交换设置(optional)。type为OFPT_SET_CONFIG/OFPT_GET_CONFIG。
    struct set_config_message {
        uint16_t flags; // IP碎片处理方法
        uint16_t miss_send_len; // Table-miss数据包个数
    }
    
    enum set_config_flag {
        OFPC_FRAG_NORMAL = 0, // 依据流表处理
        OFPC_FRAG_DROP, // 丢弃
        OFPC_FRAG_REASM, // 重组
        OFPC_FRAG_MASK // unknown
    }
    
    1. 其他可能交换的内容 STATS,QUEUE_GET_CONFIG,Vendor, …

    Flow-Mod

    对于流表进行修改的消息,可以进行添加、删除和变更设置等操作。 种类包括以下几种

    1. OFPFC_ADD
    2. OFPFC_MODIFY
    3. OFPFC_MODIFY_STRICT
    4. OFPFC_DELETE
    5. OFPFC_DELETE_STRICT 后面加上STRICT表示要完全匹配。如果有错误发生,那么将回复错误信息。如果在流表中存在相同项,那么会将原有计数器清零。
    struct flow_mod_message {
        struct ofp_match match; // 数据包匹配信息
        uint8_t cookie[64];
        uint8_t action[??]; 
        
    }
    
    struct ofp_match {
        uint32_t wildcards; // 无视哪个字段的通配符
        uint16_t in_port; // 输入物理端口
        uint8_t dl_src[OFP_ETH_ALEN]; // 源MAC地址
        uint8_t dl_dst[OFP_ETH_ALEN]; // 目的MAC地址
        uint16_t dl_vlan; // VLAN id
        uint8_t dl_vlan_pcp;  // VLAN 优先级
        uint8_t pad1; 
        uint16_t dl_type; // 以太网帧类型 
        uint8_t nw_tos; // ToS字段
        uint8_t nw_proto; // IP协议号
        uint16_t pad2;
        uint32_t nw_src; // 源IPv4地址
        uint32_t nw_dst; // 目的IPv4地址
        uint16_t tp_src; // 源端口或ICMP类型
        uint16_t tp_dst; // 目的端口或ICMP代码
    }
    
    struct flow_mod_action {
        uint16_t command; // 即上面提到的action种类
        uint16_t idle_timeout; // 如果本条规则在idle_timeout时间内没有应用成功,那么将删除该规则
        uint16_t hard_timeout; // 如果本条规则在hard_timeout时间内还未添加成功,那么将取消添加该规则。
        uint16_t priority; // 规则优先级
        uint32_t buffer_id; // 缓存ID
        uint16_t out_port; // 输出端口号
        uint16_t flags;
        uint8_t actions[??]; // TLV结构,一个头部包含修改种类,后面接上具体的数据
    }
    

    Packet-In

    Packet-In消息用于将到达OpenFlow交换机的数据包发送至OpenFlow交换机。根据交换机是否缓存数据包来设置buffer_id的值:不缓存则设置为-1,将整个数据包发送;缓存则会根据SET_CONFIG消息设置的miss_send_len为最大值的数据包发送,默认值为128。

    struct packet_in_message {
        uint32_t buffer_id; // 缓存ID
        uint16_t total_len; // 帧长度
        uint16_t in_port; // 接受帧端口
        uint8_t reason; // 发送原因(不匹配0,流表指定1)
        uint8_t pad;
        uint8_t data[??]; // 具体数据包
    }
    

    Packet-Out

    Packet-Out即为控制器向交换机发送的消息。

    struct packet_out_message {
        uint32_t buffer_id; // 缓存ID
        uint16_t in_port; // 输入端口,用OFPP_NONE表示未指定,用OFPP_CONTROLLER表示是控制器创建的数据包
        uint16_t actions_len;
        uint8_t actions[actions_len]; // 类似于Flow-Mod时的action
        uint8_t data[??]; // 当buffer_id为-1的时候,即不指定交换器缓存时
    }
    

    Port-Status

    在OpenFlow交换机添加、删除或修改物理端口时,需要发送Port-Status消息来通知OpenFlow控制器。

    struct port_status_message {
        uint8_t reason; // OFPPR_ADD(0)、OFPPR_DELETE(1)、OFPPR_MODIFY(2)
        uint8_t pad[56];
        struct ofp_phy_port; 
    }
    

    Flow-Removed

    当OpenFlow交换机设置的流表项超时时,会向控制器发送Flow-Removed消息。

    struct flow_remove_message {
        struct ofp_match match;
        uint8_t cookie[64];
        uint16_t priority;
        uint8_t reason; // OFPRR_IDLE_TIMEOUT(0)、OFPRR_HARD_TIMEOUT(1)、OFPRR_DELETE(2)
        uint8_t pad;
        uint32_t duration_sec; // 有效时间(秒)
        uint32_t duration_nsec; // 纳秒
        uint16_t idle_timeout;
        uint8_t pad2;
        uint64_t packet_count; // 数据包数
        uint64_t byte_count; // 总字节数
    }
    

    大部分字段可以直接复制于Flow-Mod的信息。

    Error

    当在处理过程中出现错误的时候发送,控制器和交换机均可使用,type为OFPT_ERROR_MSG。

    struct error_message {
        uint16_t type;
        uint16_t code;
        uint8_t data[??];
    }
    
    enum error_type {
        OFPET_HELLO_FAILED,
        OFPET_BAD_REQUEST,
        OFPET_BAD_ACTION,
        OFPET_FLOW_MOD_FAILED,
        OFPET_QUEUE_OP_FAILED
    }
    

    具体code参考OpenFlow说明书。

    Barrier

    用于双方对于事务的完成程度的通信,避免发生有顺序的事务因执行程度未达到而造成错误的情况。例如在发送了三条Flow-Mod信息,xid分别为1、2、3后,可以发送xid为4的Barrier请求,如果可以得到对方xid为4的Barrier响应,则表示前面的消息已经处理完毕。

    Echo

    用于测试两方的连接情况、通信延迟和通信带宽等。

    LLDP

    Link Layer Discovery Protocol

    IEEE 802.1ab

    许多的OpenFlow控制器利用LLDP来检测网络拓扑结构。一般来说,OpenFlow控制器会利用Packet-Out消息向OpenFlow交换机下达发送LLDP帧的命令,接下来利用接收到LLDP帧的OpenFlow交换机向控制器发送的Packet-In消息中得到拓扑消息,构建整个网络拓扑结构。

    LLDP的机制

    • 每隔一段时间(标准中建议是30秒)向L2的组播地址发送LLDP帧,这个特性无视链路状态。
    • 发送源地址为发送设备的MAC地址。
    • 单向发送设备的识别号、端口识别号,这样接收方就可以得知发送设备和端口的信息。

    LLDP使用三种全局性组播群地址

    名称 数值 说明
    Nearest Bridge 01-80-C2-00-00-0E 所有的桥和路由设备不转发
    Nearest non-TPMR Bridge 01-80-C2-00-00-C3 跨越TPMR桥
    Nearest Customer Bridge 01-80-C2-00-00-00 跨越TPMR和S-VLAN桥

    一般来说,仅OpenFlow交换机构成的网络拓扑,会使用Nearest Bridge地址,而如果在中间存在有非OpenFlow的普通L2交换机则使用Nearest non-TPMR Bridge地址,借助WAN远程使用OpenFlow交换机则使用Nearest Customer Bridge地址。

    -----------------------------------------------------
    | 组播地址 | 设备的以太网地址 | 0x88cc(LLDP) | LLDPDU |
    -----------------------------------------------------
    

    OpenFlow 1.1 更新

    头字段变更为匹配字段

    在匹配字段中添加了MPLS标签、MPLS流量类别、元数据

    多级流表

    OpenFlow交换机可以设置多个流表,还可为一个数据包匹配多个流表项,但是在流表内还是只选择一个流表项

    采用流水线处理的方式,新定义了“行动集”的概念,即将所有的行动统一添加到行动集中,但是执行顺序与计入顺序不相同,采用copy TTL inwards->pop->push->copy TTL outwards->decrement TTL->set->qos->group->output的方式,这里面每种类型仅能设置一个。

    如果发生了Table-miss的情况,那么将根据OFPT_TABLE_MOD对流表的设置来决定,具体的方法有发送至控制器、前进到下一流表和丢弃。

  • ONOS官方示例应用解析

    onos-app-calendar

    一个RESTful的web应用,提供添加链路时延、带宽限制的Intent。用到了ConnectivityIntent。完成功能的逻辑

        /**
         * Create an Intent for a bidirectional path with constraints.
         *
         * @param key optional intent key
         * @param src the path source (DPID or hostID)
         * @param dst the path destination (DPID or hostID)
         * @param srcPort the source port (-1 if src/dest is a host)
         * @param dstPort the destination port (-1 if src/dest is a host)
         * @param bandwidth the bandwidth (mbps) requirement for the path
         * @param latency the latency (micro sec) requirement for the path
         * @return the appropriate intent
         */
        private Intent createIntent(Key key,
                                    String src,
                                    String dst,
                                    String srcPort,
                                    String dstPort,
                                    Long bandwidth,
                                    Long latency) {
    
            TrafficSelector selector = buildTrafficSelector();
            TrafficTreatment treatment = builder().build();
    
            final Constraint constraintBandwidth =
                    new BandwidthConstraint(Bandwidth.mbps(bandwidth));
            final Constraint constraintLatency =
                    new LatencyConstraint(Duration.of(latency, ChronoUnit.MICROS));
            final List<Constraint> constraints = new LinkedList<>();
    
            constraints.add(constraintBandwidth);
            constraints.add(constraintLatency);
    
            if (srcPort.equals("-1")) {
                HostId srcPoint = HostId.hostId(src);
                HostId dstPoint = HostId.hostId(dst);
                return HostToHostIntent.builder()
                        .appId(appId())
                        .key(key)
                        .one(srcPoint)
                        .two(dstPoint)
                        .selector(selector)
                        .treatment(treatment)
                        .constraints(constraints)
                        .build();
    
            } else {
                ConnectPoint srcPoint = new ConnectPoint(deviceId(src), portNumber(srcPort));
                ConnectPoint dstPoint = new ConnectPoint(deviceId(dst), portNumber(dstPort));
                return TwoWayP2PIntent.builder()
                        .appId(appId())
                        .key(key)
                        .one(srcPoint)
                        .two(dstPoint)
                        .selector(selector)
                        .treatment(treatment)
                        .constraints(constraints)
                        .build();
            }
        }
    
    
        /**
         * Synchronously submits an intent to the Intent Service.
         *
         * @param intent intent to submit
         * @return true if operation succeed, false otherwise
         */
        private boolean submitIntent(Intent intent)
                throws InterruptedException {
            IntentService service = get(IntentService.class);
    
            CountDownLatch latch = new CountDownLatch(1);
            InternalIntentListener listener = new InternalIntentListener(intent, service, latch);
            service.addListener(listener);
            service.submit(intent);
            log.info("Submitted Calendar App intent and waiting: {}", intent);
            if (latch.await(TIMEOUT, TimeUnit.SECONDS) &&
                    listener.getState() == INSTALLED) {
                return true;
            }
            return false;
        }
    
    

    onos-app-carrierethernet

    根据pom.xml文件的内容,我们可以知道这个应用是用于运营以太网服务(Carrier Ethernet),具体介绍在Carrier Ethernet,由城域以太网论坛(MEF)建立。CE包含五个模块:保护、QoS、扩展、业务管理、TDM。个人理解是,希望以太网上的流量,能够在SDN环境下加入一定的识别特征,这样才能方便城域网中的网络设备根据这些特征进行服务的定制。这个项目相对来说比较复杂,而且更新也非常的频繁,先挖个坑在这里,以后有时间的话可以认真阅读一下其实现。

    onos-app-database-perf

    一个用来测试ONOS集群存储数据性能的应用,在activate中包含有对于StorageService的使用,包括创建并发式Map的方法,注册序列化器KryoNamespace等,之后创建多个线程测试具体的性能指标。

    onos-app-ecord-co

    一个CORD的实现应用,CORD(Central Office Re-architected as a DataCenter),意为在家庭、公司等网络边界的基础网络设备,实现一个数据中心的服务功能。CORD目前有三种类型:ECORD、RCORD和MCORD,具体的详情可以查看CORD。这个应用给出了一个ECORD的实现方案。这个应用比较全面地展示了ONOS的抽象子系统概念,我们可以从项目的结构看出来。

    CentralOffice.java文件是组件的主文件,主要工作是注册应用,并且创建了一个名为BigSwitchDeviceProvider的对象,那么接下来我们去找找实现。BigSwitchDeviceProvider继承于DeviceProvider。从前面两次的loadConfig操作中我们可以看出来这个应用支持修改config文件,并且提供有RPC和RESTful两种修改模式,然而目前我对于ConfigService还没有太深的了解,因此这里填个坑等到以后再来了解。在activate方法中基本就是向deviceProvider中注册了一个设备,接下来使用LLDP协议发现网络拓扑。为了完成LLDP的工作,程序中使用到了onlab-misc中的ONOSLLDP工具类,方便使用LLDP协议。

        @Activate
        public void activate(ComponentContext context) {
            cfgService.registerProperties(getClass()); // 在ComponentConfigService上进行注册
            loadRpcConfig(context); 
            loadRestConfig(context); 
    
            // setup service to, and register with, providers
            try {
                remoteServiceContext = rpcService.get(URI.create(remoteUri));
            } catch (UnsupportedOperationException e) {
                log.warn("Unsupported URI: {}", remoteUri);
            }
            providerId = new ProviderId(schemeProp, idProp);
            executor = newSingleThreadScheduledExecutor(groupedThreads("onos/bigswitch", "discovery-%d"));
            registerToDeviceProvider();
            prepareProbe();
            registerToLinkServices();
    
            // start listening to config changes
            NetworkConfigListener cfglistener = new InternalConfigListener();
            cfgRegistry.addListener(cfglistener);
            cfgRegistry.registerConfigFactory(xcConfigFactory);
            log.info("Started");
        }
    
        @Deactivate
        public void deactivate() {
            packetService.removeProcessor(packetProcessor);
    
    
            // advertise all Links as vanished
            knownLinks.invalidateAll();
    
            cfgRegistry.unregisterConfigFactory(xcConfigFactory);
            cfgService.unregisterProperties(getClass(), false);
            unregisterFromLinkServices();
            executor.shutdownNow();
            unregisterFromDeviceProvider();
            // Won't hurt but necessary?
            deviceProviderService = null;
            providerId = null;
            log.info("Stopped");
        }
    
        @Modified
        public void modified(ComponentContext context) {
            log.info("Reloading config...");
            // Needs re-registration to DeviceProvider
            if (loadRpcConfig(context)) {
                // unregister from Device and Link Providers with old parameters
                unregisterFromLinkServices();
                unregisterFromDeviceProvider();
                // register to Device and Link Providers with new parameters
                try {
                    remoteServiceContext = rpcService.get(URI.create(remoteUri));
                    providerId = new ProviderId(schemeProp, idProp);
                    registerToDeviceProvider();
                    registerToLinkServices();
                } catch (UnsupportedOperationException e) {
                    log.warn("Unsupported URI: {}", remoteUri);
                }
                log.info("Re-registered with Device and Link Providers");
            }
    
            // Needs to advertise cross-connect links
            if (loadRestConfig(context)) {
                advertiseCrossConnectLinksOnAllPorts();
            }
        }
    
    
  • mininet 学习

    命令语法

    $ 这个符号代表现在处于 Linux 的shell 交互下,需要使用的是 Linux 命令 mininet> 这个符号表示现在处于 Mininet 交互下,需要使用的是 Mininet 的命令 # 这个符号表示的是现在处于 Linux 的 root 权限下。 sudo mn -h 命令用于显示mininet的帮助信息 Mininet使用基于过程虚拟化和网络命名空间的特性来创建虚拟网络,并且创建的网络在当前的Linux内核中是可用的

    sudo mn 启动Mininet

    查看全部节点:nodes

    查看链路信息:net

    输出各节点信息:dump

    sudo mn –test pingpair 直接对主机连通性进行测试

    sudo mn –test iperf启动后直接进行性能测试

    主机端

    sudo mn -x ,通过使用-x参数,Mininet在启动后会在每个节点上自动打开一个XTerm,方便某些情况下对多个节点分别进行操作。

    在进入mn cli 之后,也可以使用xterm node(s1 h2)命令指定启动某些节点上的xterm,如分别启动s1和h2上的xterm

    禁用或启用某条链路,格式为:link node1 node2 up/down

    –switch选项和–controller选项,可以指定采用哪种类型的交换机跟控制器

    –innamespace参数,可以让所有结点拥有各自的名字空间

    启动参数总结 -h, –help 打印帮助信息

    –switch=SWITCH 交换机类型,包括 [kernel user ovsk]

    –host=HOST 模拟主机类型,包括 [process]

    –controller=CONTROLLER 控制器类型,包括 [nox_dump none ref remote nox_pysw]

    –topo=TOPO,arg1,arg2,…argN 指定自带拓扑,包括 [tree reversed single linear minimal]

    -c, –clean清理环境

    –custom=CUSTOM 使用自定义拓扑和节点参数

    –test=TEST 测试命令,包括 [cli build pingall pingpair iperf all iperfudp none]

    -x, –xterms 在每个节点上打开 xterm

    –mac 让MAC 地址跟 DP ID 相同

    –arp 配置所有 ARP 项

    -v VERBOSITY, –verbosity=VERBOSITY [info warning critical error debug output] 输出日志级别

    –ip=IP 远端控制器的IP地址

    –port=PORT 远端控制器监听端口

    –innamespace 在独立的名字空间内

    –listenport=LISTENPORT 被动监听的起始端口

    –nolistenport 不使用被动监听端口

    –pre=PRE 测试前运行的 CLI 脚本

    –post=POST 测试后运行的 CLI 脚本

    常用命令总结 help 默认列出所有命令文档,后面加命令名将介绍该命令用法

    dump 打印节点信息

    gterm 给定节点上开启 gnome-terminal。注:可能导致 Mininet 崩溃

    xterm 给定节点上开启 xterm

    intfs 列出所有的网络接口

    iperf 两个节点之间进行简单的 iperf TCP测试

    iperfudp 两个节点之间用指定带宽 udp 进行测试

    net 显示网络链接情况

    noecho 运行交互式窗口,关闭回应(echoing)

    pingpair 在前两个主机之间互 ping 测试

    source 从外部文件中读入命令

    dpctl 在所有交换机上用 dptcl 执行相关命令,本地为 tcp 127.0.0.1:6634

    link 禁用或启用两个节点之间的链路

    nodes 列出所有的节点信息

    pingall 所有 host 节点之间互 ping

    py 执行 Python 表达式

    sh 运行外部 shell 命令

    quit/exit 退出

  • org.onosproject.fwd 应用解析

    ONOS 二层转发应用

    org.onosproject.fwd应用应该说是ONOS中最核心的应用了,要想让我们创建的Mininet虚拟网络实现二层互通,就需要激活这个官方应用,因此从这个应用中我们能够学习到ONOS对网络的抽象方式,以及二层转发功能实现方式。截至本文发布之时ONOS的最新版本是1.13.0-SNAPSHOT,因此这里的源码也截至最新开发版。首先我们还是看一下应用的pom.xml文件。

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <parent>
            <groupId>org.onosproject</groupId>
            <artifactId>onos-apps</artifactId>
            <version>1.13.0-SNAPSHOT</version>
        </parent>
    
        <artifactId>onos-app-fwd</artifactId>
        <packaging>bundle</packaging>
    
        <description>Reactive forwarding application using flow subsystem</description>
    
        <properties>
            <onos.app.name>org.onosproject.fwd</onos.app.name>
            <onos.app.title>Reactive Forwarding App</onos.app.title>
            <onos.app.category>Traffic Steering</onos.app.category>
            <onos.app.url>http://onosproject.org</onos.app.url>
            <onos.app.readme>Reactive forwarding application using flow subsystem.</onos.app.readme>
        </properties>
    
        <dependencies>
            ...
        </dependencies>
    
    </project>
    
    

    根据简介,我们可以得知这里的二层转发方式是使用Flow子系统来实现的,具体细化一下应该是FlowObjectiveService。更加详尽的介绍我们可以在BUCK文件中找到(值得一提的是,ONOS已经使用BUCK作为默认构建方式,虽然Maven仍然可以使用)。

    Provisions traffic between end-stations using hop-by-hop flow programming by intercepting packets for which there are currently no matching flow objectives on the data plane. 
    The paths paved in this manner are short-lived, i.e. they expire a few seconds after the flow on whose behalf they were programmed stops. 
    The application relies on the ONOS path service to compute the shortest paths. 
    In the event of negative topology events (link loss, device disconnect, etc.), the application will proactively invalidate any paths that it had programmed to lead through the resources that are no longer available.
    

    应用结构

    org.onosproject.fwd下包含几个文件

    MacAddressCompleter.java
    ReactiveForwarding.java
    ReactiveForwardingCommand.java
    ReactiveForwardMetrics.java
    

    MacAddressCompleter

    public class MacAddressCompleter implements Completer {
        @Override
        public int complete(String buffer, int cursor, List<String> candidates) {
            // Delegate string completer
            StringsCompleter delegate = new StringsCompleter();
            EventuallyConsistentMap<MacAddress, ReactiveForwardMetrics> macAddress;
            // Fetch our service and feed it's offerings to the string completer
            ReactiveForwarding reactiveForwardingService = AbstractShellCommand.get(ReactiveForwarding.class);
            macAddress = reactiveForwardingService.getMacAddress();
            SortedSet<String> strings = delegate.getStrings();
            for (MacAddress key : macAddress.keySet()) {
                strings.add(key.toString());
            }
            // Now let the completer do the work for figuring out what to offer.
            return delegate.complete(buffer, cursor, candidates);
        }
    }
    

    很容易看出来,这个类是用来CLI下补全MAC地址的,在resources/OSGI-INF.blueprint/shell-config.xml文件中我们可以看到命令的定义方式。

    <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
        <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
            <command>
                <action class="org.onosproject.fwd.ReactiveForwardingCommand"/>
                <completers>
                <ref component-id="MacAddressCompleter"/>
                </completers>
            </command>
        </command-bundle>
        <bean id="MacAddressCompleter" class="org.onosproject.fwd.MacAddressCompleter"/>
    </blueprint>
    

    Completer声明为一个bean,引用在org.onosproject.fwd.ReactiveForwardingCommand中。那么我们接下来看一下Command的实现。

    ReactiveForwardingCommand

    @Command(scope = "onos", name = "reactive-fwd-metrics",
            description = "List all the metrics of reactive fwd app based on mac address")
    public class ReactiveForwardingCommand extends AbstractShellCommand {
        @Argument(index = 0, name = "mac", description = "One Mac Address",
                required = false, multiValued = false)
        String mac = null;
        @Override
        protected void execute() {
            ReactiveForwarding reactiveForwardingService = AbstractShellCommand.get(ReactiveForwarding.class);
            MacAddress macAddress = null;
            if (mac != null) {
                macAddress = MacAddress.valueOf(mac);
            }
            reactiveForwardingService.printMetric(macAddress);
        }
    }
    

    可以看到,这里用注解创建了一个CLI命令,名为onos:reactive-fwd-metrics,后面加mac地址,可以打印出对应主机的metrics。

    onos> onos:reactive-fwd-metrics aa:0e:a8:c8:c9:a8
    -----------------------------------------------------------------------------------------
     MACADDRESS 						 Metrics
     AA:0E:A8:C8:C9:A8 			 null
    

    ReactiveForwardMetrics

    public class ReactiveForwardMetrics {
        private Long replyPacket = null;
        private Long inPacket = null;
        private Long droppedPacket = null;
        private Long forwardedPacket = null;
        private MacAddress macAddress;
    }
    

    可以看到,ReactiveForwardMetrics这个应用是用来统计Packet的处理情况的,上面的代码中省略了对数量进行更新的函数以及toString函数。

    ReactiveForwarding

    这个类是fwd应用中最核心的文件,实现了转发的具体逻辑。