:::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:

:::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/)