---
# System prepended metadata

title: 'Lab 3 ONOS Application Development: SDN-enabled Learning Bridge and Proxy ARP (part 1)'
tags: [SDN+cloud-native]

---

:::danger
下方僅供參考，非官方標準答案。實際執行可能會因為軟體版本不同導致，而出現 dependency 的或是語法差異等......問題。身為 CS 人應該具有 debug、troubleshooting 的能力
:::

# Lab 3 ONOS Application Development: SDN-enabled Learning Bridge and Proxy ARP
:::spoiler 文章目錄
[TOC]

:::

:::info
我的實作環境：
- Ubuntu 22.04 desktop
- 4 core CPU
- 4 GB RAM
- 30GB Disk
- 我把 ONOS application `nycu.winlab.bridge` 安裝在 home 目錄

溫馨小提醒，你在寫這個作業時，可能會需要：
- 複製 pdf 當中的指令時，**請注意有沒有不小心多複製到一個空格**。
- tree 用來看 Directory：`sudo apt install tree`
    - 舉例：查看 bridge-app 的 directory 指令：`tree ~/bridge-app`
- 清理 topology，使用 `exit` 離開 mininet 之後，記得養成習慣執行 `sudo mn -c`
:::


## Correct naming convention in pom.xml (5%)

> 請注意，根據 Java 的命名規範 `onos.app.name` 的命名，需要全部都是小寫。[color=#e51d53]

> 這個 pom.xml 程式碼中，**並沒有把 checkstyle 程式碼檢查工具註解掉（但你可以手動註解-推薦）**。checkstyle 是用來檢查 java code 的，它的好處是除了可以提高程式碼的可讀性之外，還可以確保其他人和原作者在編譯此篇中所有程式碼時，是保持一致的[color=#e51d53]
> 缺點：
> - 不能使用「tab」鍵。
> - 單行長度不可以超過 120 字符，超過則需要換行

:::spoiler 點我看程式碼
```xml=
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.onosproject</groupId>
        <artifactId>onos-dependencies</artifactId>
        <version>2.7.0</version>
    </parent>

    <groupId>nycu.winlab</groupId>
    <artifactId>bridge-app</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>bundle</packaging>

    <description>ONOS OSGi bundle archetype</description>
    <url>http://onosproject.org</url>

    <properties>
        <onos.app.name>nycu.winlab.bridge</onos.app.name>
        <onos.app.title>Learning Bridge App</onos.app.title>
        <onos.app.origin>Winlab, NYCU</onos.app.origin>
        <onos.app.category>default</onos.app.category>
        <onos.app.url>http://onosproject.org</onos.app.url>
        <onos.app.readme>ONOS OSGi bundle archetype.</onos.app.readme>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.onosproject</groupId>
            <artifactId>onos-api</artifactId>
            <version>${onos.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.onosproject</groupId>
            <artifactId>onlab-osgi</artifactId>
            <version>${onos.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.onosproject</groupId>
            <artifactId>onlab-misc</artifactId>
            <version>${onos.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.onosproject</groupId>
            <artifactId>onos-api</artifactId>
            <version>${onos.version}</version>
            <scope>test</scope>
            <classifier>tests</classifier>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.onosproject</groupId>
                <artifactId>onos-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <!-- Uncomment to disable checkstyle validation
                <configuration>
                    <skip>true</skip>
                </configuration>
                -->
            </plugin>
        </plugins>
    </build>

</project>
```
:::

## Learning bridge function is available (20%)

### 用於測試 Learning bridge function 的 topology
- Learning Bridge Function with tree **(depth=2)** topology.

```bash=
sudo mn --controller=remote,127.0.0.1:6653 \
--topo=tree,depth=2 \
--switch=ovs,protocols=OpenFlow14
```
- Learning Bridge Function with tree **(depth=4)** topology.
```bash=
sudo mn --controller=remote,127.0.0.1:6653 \
--topo=tree,depth=4 \
--switch=ovs,protocols=OpenFlow14
```

:::spoiler 點我看程式碼
```java=
package nycu.winlab.bridge;

import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketPriority;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onlab.packet.Ethernet;
import org.onlab.packet.MacAddress;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

@Component(immediate = true)
public class AppComponent {

    private final Logger log = LoggerFactory.getLogger(getClass());

    /** Some configurable property. */

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected ComponentConfigService cfgService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected CoreService coreService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected PacketService packetService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected FlowRuleService flowRuleService;

    private LearningBridgeProcessor processor = new LearningBridgeProcessor();
    private ApplicationId appId;
    private Map<DeviceId, Map<MacAddress, PortNumber>> bridgeTable = new HashMap<>();

    @Activate
    protected void activate() {

        // register your app
        appId = coreService.registerApplication("nycu.winlab.bridge");

        // add a packet processor to packetService
        packetService.addProcessor(processor, PacketProcessor.director(2));

        // install a flowrule for packet-in
        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
        selector.matchEthType(Ethernet.TYPE_IPV4);
        packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);

        log.info("Started");
    }

    @Deactivate
    protected void deactivate() {

        // remove flowrule installed by your app
        flowRuleService.removeFlowRulesById(appId);

        // remove your packet processor
        packetService.removeProcessor(processor);
        processor = null;

        // remove flowrule you installed for packet-in
        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
        selector.matchEthType(Ethernet.TYPE_IPV4);
        packetService.cancelPackets(selector.build(), PacketPriority.REACTIVE, appId);

        log.info("Stopped");
    }

    private class LearningBridgeProcessor implements PacketProcessor {

        @Override
        public void process(PacketContext context) {
            // Stop processing if the packet has been handled, since we
            // can't do any more to it.
            if (context.isHandled()) {
                return;
            }
            InboundPacket pkt = context.inPacket();
            Ethernet ethPkt = pkt.parsed();

            if (ethPkt == null) {
                return;
            }

            DeviceId recDevId = pkt.receivedFrom().deviceId();
            PortNumber recPort = pkt.receivedFrom().port();
            MacAddress srcMac = ethPkt.getSourceMAC();
            MacAddress dstMac = ethPkt.getDestinationMAC();

            // rec packet-in from new device, create new table for it
            if (bridgeTable.get(recDevId) == null) {
                bridgeTable.put(recDevId, new HashMap<>());
            }

            if (bridgeTable.get(recDevId).get(srcMac) == null) {
                bridgeTable.get(recDevId).put(srcMac, recPort);
                // the mapping of pkt's src mac and receivedfrom port wasn't store in the table of the rec device
            }

            if (bridgeTable.get(recDevId).get(dstMac) == null) {
                // the mapping of dst mac and forwarding port wasn't store in the table of the rec device
                flood(context);
            } else {
                PortNumber outPort = bridgeTable.get(recDevId).get(dstMac);
                installRule(context, outPort, dstMac);
            }
        }
    }

    private void installRule(PacketContext context, PortNumber outPort, MacAddress dstMac) {
        // Build match
        TrafficSelector selector = DefaultTrafficSelector.builder()
                .matchEthDst(dstMac)
                .build();

        // Build action
        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                .setOutput(outPort)
                .build();

        // Build flow rule
        FlowRule flowRule = DefaultFlowRule.builder()
                .forDevice(context.inPacket().receivedFrom().deviceId())
                .withSelector(selector)
                .withTreatment(treatment)
                .withPriority(10)
                .fromApp(appId)
                .makeTemporary(30)
                .build();

        // Apply flow rule
        flowRuleService.applyFlowRules(flowRule);

        // Forward the packet
        packetOut(context, outPort);
    }

    // Add the flood method
    private void flood(PacketContext context) {
        // Flood the packet out of all ports except the incoming port
        packetOut(context, PortNumber.FLOOD);
    }

    // Corrected packetOut method
    private void packetOut(PacketContext context, PortNumber outPort) {
        context.treatmentBuilder().setOutput(outPort);
        context.send();
    }
}

```
:::

## Logs are in the correct format (10%)
是基於上面的 code 來進行調整，新增 `log.info()` 功能 to record actions done by your application.

:::spoiler 點我看程式碼
```java=
package nycu.winlab.bridge;

import org.onlab.packet.Ethernet;
import org.onlab.packet.MacAddress;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketPriority;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

@Component(immediate = true)
public class AppComponent {

    private final Logger log = LoggerFactory.getLogger(getClass());

    /** Some configurable property. */

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected ComponentConfigService cfgService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected CoreService coreService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected PacketService packetService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected FlowRuleService flowRuleService;

    private LearningBridgeProcessor processor = new LearningBridgeProcessor();
    private ApplicationId appId;
    private Map<DeviceId, Map<MacAddress, PortNumber>> bridgeTable = new HashMap<>();

    @Activate
    protected void activate() {

        // register your app
        appId = coreService.registerApplication("nycu.winlab.bridge");

        // add a packet processor to packetService
        packetService.addProcessor(processor, PacketProcessor.director(2));

        // install a flowrule for packet-in
        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
        selector.matchEthType(Ethernet.TYPE_IPV4);
        packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);

        log.info("Started");
    }

    @Deactivate
    protected void deactivate() {

        // remove flowrule installed by your app
        flowRuleService.removeFlowRulesById(appId);

        // remove your packet processor
        packetService.removeProcessor(processor);
        processor = null;

        // remove flowrule you installed for packet-in
        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
        selector.matchEthType(Ethernet.TYPE_IPV4);
        packetService.cancelPackets(selector.build(), PacketPriority.REACTIVE, appId);

        log.info("Stopped");
    }

    private class LearningBridgeProcessor implements PacketProcessor {

        @Override
        public void process(PacketContext context) {
            // Stop processing if the packet has been handled, since we
            // can't do any more to it.
            if (context.isHandled()) {
                return;
            }
            InboundPacket pkt = context.inPacket();
            Ethernet ethPkt = pkt.parsed();

            if (ethPkt == null) {
                return;
            }

            DeviceId recDevId = pkt.receivedFrom().deviceId();
            PortNumber recPort = pkt.receivedFrom().port();
            MacAddress srcMac = ethPkt.getSourceMAC();
            MacAddress dstMac = ethPkt.getDestinationMAC();

            // rec packet-in from new device, create new table for it
            if (bridgeTable.get(recDevId) == null) {
                bridgeTable.put(recDevId, new HashMap<>());
            }

            if (bridgeTable.get(recDevId).get(srcMac) == null) {
                bridgeTable.get(recDevId).put(srcMac, recPort);
                log.info("Add an entry to the port table of {}. MAC address: {} => Port: {}.",
        recDevId, srcMac, recPort);

            }

            if (bridgeTable.get(recDevId).get(dstMac) == null) {
                log.info("MAC address {} is missed on {}. Flood the packet.", dstMac, recDevId);
                flood(context);
            } else {
                PortNumber outPort = bridgeTable.get(recDevId).get(dstMac);
                log.info("MAC address {} is matched on {}. Install a flow rule.", dstMac, recDevId);
                installRule(context, outPort, dstMac);
            }
        }
    }

    private void installRule(PacketContext context, PortNumber outPort, MacAddress dstMac) {
        // Build match
        TrafficSelector selector = DefaultTrafficSelector.builder()
                .matchEthDst(dstMac)
                .build();

        // Build action
        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                .setOutput(outPort)
                .build();

        // Build flow rule
        FlowRule flowRule = DefaultFlowRule.builder()
                .forDevice(context.inPacket().receivedFrom().deviceId())
                .withSelector(selector)
                .withTreatment(treatment)
                .withPriority(10)
                .fromApp(appId)
                .makeTemporary(30)
                .build();

        // Apply flow rule
        flowRuleService.applyFlowRules(flowRule);

        // Forward the packet
        packetOut(context, outPort);
    }

    // Add the flood method
    private void flood(PacketContext context) {
        // Flood the packet out of all ports except the incoming port
        packetOut(context, PortNumber.FLOOD);
    }

    // Corrected packetOut method
    private void packetOut(PacketContext context, PortNumber outPort) {
        context.treatmentBuilder().setOutput(outPort);
        context.send();
    }
}

```
:::

## Flow rules in hosts are comply with the regulations (5%)

題目需求
```
Rule requirements:
• Match field (selector): ETH_SRC, ETH_DST
• Action field (treatment): OUTPUT
• Flow priority: 30
• Flow timeout: 30
• You earn credits only if all flow rules are correct
```

**You are only allowed to use below Java API(classes package) to install flow rules on network devices.**
- `org.onosproject.net.flowobjective`
- `org.onosproject.net.flow` 

> Everyone should really understand how to use both packages to install flow rules.**（希望你兩個都可以學會使用）**。[color=#e51d53]

And We will test your application with only the following applications activated:

![image](https://hackmd.io/_uploads/Hy3q9Gfekx.png)

:::spoiler 點我看程式碼（同時使用 FlowObjectiveService 和 FlowRuleService）
```java=
package nycu.winlab.bridge;

import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.DeviceId;
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flowobjective.DefaultForwardingObjective;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketPriority;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onlab.packet.Ethernet;
import org.onlab.packet.MacAddress;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

@Component(immediate = true)
public class AppComponent {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected ComponentConfigService cfgService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected CoreService coreService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected PacketService packetService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected FlowRuleService flowRuleService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected FlowObjectiveService flowObjectiveService;

    private LearningBridgeProcessor processor = new LearningBridgeProcessor();
    private ApplicationId appId;
    private Map<DeviceId, Map<MacAddress, PortNumber>> bridgeTable = new HashMap<>();

    @Activate
    protected void activate() {
        appId = coreService.registerApplication("nycu.winlab.bridge");
        packetService.addProcessor(processor, PacketProcessor.director(2));
        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
        selector.matchEthType(Ethernet.TYPE_IPV4);
        packetService.requestPackets(selector.build(), PacketPriority.REACTIVE, appId);
        log.info("Started");
    }

    @Deactivate
    protected void deactivate() {
        flowRuleService.removeFlowRulesById(appId);
        packetService.removeProcessor(processor);
        processor = null;
        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
        selector.matchEthType(Ethernet.TYPE_IPV4);
        packetService.cancelPackets(selector.build(), PacketPriority.REACTIVE, appId);
        log.info("Stopped");
    }

    private class LearningBridgeProcessor implements PacketProcessor {
        @Override
        public void process(PacketContext context) {
            if (context.isHandled()) {
                return;
            }
            InboundPacket pkt = context.inPacket();
            Ethernet ethPkt = pkt.parsed();
            if (ethPkt == null) {
                return;
            }

            DeviceId recDevId = pkt.receivedFrom().deviceId();
            PortNumber recPort = pkt.receivedFrom().port();
            MacAddress srcMac = ethPkt.getSourceMAC();
            MacAddress dstMac = ethPkt.getDestinationMAC();

            bridgeTable.computeIfAbsent(recDevId, k -> new HashMap<>());

            Map<MacAddress, PortNumber> portTable = bridgeTable.get(recDevId);
            portTable.putIfAbsent(srcMac, recPort);

            PortNumber outPort = portTable.getOrDefault(dstMac, PortNumber.FLOOD);

            if (outPort.equals(PortNumber.FLOOD)) {
                flood(context);
            } else {
                installRule(context, outPort, srcMac, dstMac);
                installObjective(context, outPort, srcMac, dstMac);
            }
        }
    }

    private void installRule(PacketContext context, PortNumber outPort, MacAddress srcMac, MacAddress dstMac) {
        TrafficSelector selector = DefaultTrafficSelector.builder()
                .matchEthSrc(srcMac)
                .matchEthDst(dstMac)
                .build();
        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                .setOutput(outPort)
                .build();
        FlowRule flowRule = DefaultFlowRule.builder()
                .forDevice(context.inPacket().receivedFrom().deviceId())
                .withSelector(selector)
                .withTreatment(treatment)
                .withPriority(30)
                .fromApp(appId)
                .makeTemporary(30)
                .build();
        flowRuleService.applyFlowRules(flowRule);
    }

    private void installObjective(PacketContext context, PortNumber outPort, MacAddress srcMac, MacAddress dstMac) {
        TrafficSelector selector = DefaultTrafficSelector.builder()
                .matchEthSrc(srcMac)
                .matchEthDst(dstMac)
                .build();
        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                .setOutput(outPort)
                .build();
        ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
                .withSelector(selector)
                .withTreatment(treatment)
                .withPriority(30)
                .fromApp(appId)
                .makeTemporary(30)
                .add();
        flowObjectiveService.forward(context.inPacket().receivedFrom().deviceId(), forwardingObjective);
    }

    private void flood(PacketContext context) {
        packetOut(context, PortNumber.FLOOD);
    }

    private void packetOut(PacketContext context, PortNumber outPort) {
        context.treatmentBuilder().setOutput(outPort);
        context.send();
    }
}

:::

### References
- [Pin-Shao Chen's Github project SDN-NFV](https://github.com/pin-chen/SDN-NFV)
- [Install Azul Zulu on Debian-based Linux](https://docs.azul.com/core/zulu-openjdk/install/debian)
- [ONOS Wiki – Template Application Tutorial](https://wiki.onosproject.org/display/ONOS/Template+Application+Tutorial)
- [ONOS Application Subsystem](https://wiki.onosproject.org/display/ONOS/Application+Subsystem)
- [ONOS Java API (2.7.0)](https://api.onosproject.org/2.7.0/apidocs/)

