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