├── LICENSE ├── Makefile ├── README.md ├── app ├── pom.xml ├── src │ └── main │ │ └── java │ │ └── org │ │ └── onosproject │ │ └── srv6_usid │ │ ├── AppConstants.java │ │ ├── Ipv6RoutingComponent.java │ │ ├── L2BridgingComponent.java │ │ ├── MainComponent.java │ │ ├── NdpReplyComponent.java │ │ ├── Srv6Component.java │ │ ├── cli │ │ ├── RouteInsertCommand.java │ │ ├── Srv6ClearCommand.java │ │ ├── Srv6InsertCommand.java │ │ ├── Srv6SidCompleter.java │ │ └── UAInsertCommand.java │ │ ├── common │ │ ├── Srv6DeviceConfig.java │ │ └── Utils.java │ │ └── pipeconf │ │ ├── InterpreterImpl.java │ │ ├── PipeconfLoader.java │ │ └── PipelinerImpl.java └── srv6-uSID.iml ├── config ├── netcfg.json ├── routing_tables.txt ├── srv6_insert.txt └── ua_config.txt ├── docker-compose.yml ├── ecmp_encap_split.txt ├── mininet ├── bmv2.py ├── bmv2.pyc ├── ciao.pcap ├── host6.py ├── host6.pyc ├── ipv6_sr.py └── topo.py ├── p4src ├── .gitignore ├── Makefile ├── include │ ├── checksum.p4 │ ├── define.p4 │ ├── header.p4 │ └── parser.p4 └── main.p4 ├── test ├── ipv4_test_encap_1sid.pcap ├── ipv4_test_encap_2sid.pcap ├── ipv6_test_decap.pcap ├── test_dx4.txt └── test_encap.txt └── util ├── docker ├── Makefile ├── Makefile.vars ├── mvn │ └── Dockerfile └── stratum_bmv2 │ └── Dockerfile ├── gnmi-cli ├── mn-cmd ├── mn-pcap ├── oc-pb-decoder ├── onos-cmd ├── p4rt-sh └── vm ├── .gitignore ├── README.md ├── Vagrantfile ├── build-vm.sh ├── cleanup.sh ├── root-bootstrap.sh └── user-bootstrap.sh /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) 2 | curr_dir := $(patsubst %/,%,$(dir $(mkfile_path))) 3 | 4 | include util/docker/Makefile.vars 5 | 6 | onos_url := http://localhost:8181/onos 7 | onos_curl := curl --fail -sSL --user onos:rocks --noproxy localhost 8 | app_name := app.onosproject.srv6_usid 9 | 10 | NGSDN_TUTORIAL_SUDO ?= 11 | 12 | default: 13 | $(error Please specify a make target (see README.md)) 14 | 15 | _docker_pull_all: 16 | docker pull ${ONOS_IMG}@${ONOS_SHA} 17 | docker tag ${ONOS_IMG}@${ONOS_SHA} ${ONOS_IMG} 18 | docker pull ${P4RT_SH_IMG}@${P4RT_SH_SHA} 19 | docker tag ${P4RT_SH_IMG}@${P4RT_SH_SHA} ${P4RT_SH_IMG} 20 | docker pull ${P4C_IMG}@${P4C_SHA} 21 | docker tag ${P4C_IMG}@${P4C_SHA} ${P4C_IMG} 22 | docker pull ${STRATUM_BMV2_IMG}@${STRATUM_BMV2_SHA} 23 | docker tag ${STRATUM_BMV2_IMG}@${STRATUM_BMV2_SHA} ${STRATUM_BMV2_IMG} 24 | docker pull ${MVN_IMG}@${MVN_SHA} 25 | docker tag ${MVN_IMG}@${MVN_SHA} ${MVN_IMG} 26 | docker pull ${GNMI_CLI_IMG}@${GNMI_CLI_SHA} 27 | docker tag ${GNMI_CLI_IMG}@${GNMI_CLI_SHA} ${GNMI_CLI_IMG} 28 | docker pull ${YANG_IMG}@${YANG_SHA} 29 | docker tag ${YANG_IMG}@${YANG_SHA} ${YANG_IMG} 30 | docker pull ${SSHPASS_IMG}@${SSHPASS_SHA} 31 | docker tag ${SSHPASS_IMG}@${SSHPASS_SHA} ${SSHPASS_IMG} 32 | 33 | deps: _docker_pull_all 34 | 35 | start: 36 | $(info *** Starting ONOS and Mininet (${NGSDN_TOPO_PY})... ) 37 | @mkdir -p tmp/onos 38 | docker-compose up -d 39 | 40 | 41 | stop: 42 | $(info *** Stopping ONOS and Mininet...) 43 | docker-compose down 44 | 45 | restart: reset start 46 | 47 | onos-cli: 48 | $(info *** Connecting to the ONOS CLI... password: rocks) 49 | $(info *** Top exit press Ctrl-D) 50 | @ssh -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" -o LogLevel=ERROR -p 8101 onos@localhost 51 | 52 | onos-log: 53 | docker-compose logs -f onos 54 | 55 | onos-ui: 56 | open ${onos_url}/ui 57 | 58 | mn-cli: 59 | $(info *** Attaching to Mininet CLI...) 60 | $(info *** To detach press Ctrl-D (Mininet will keep running)) 61 | -@docker attach --detach-keys "ctrl-d" $(shell docker-compose ps -q mininet) || echo "*** Detached from Mininet CLI" 62 | 63 | mn-log: 64 | docker logs -f mininet 65 | 66 | netcfg: 67 | $(info *** Pushing netcfg.json to ONOS...) 68 | ${onos_curl} -X POST -H 'Content-Type:application/json' \ 69 | ${onos_url}/v1/network/configuration -d@./config/netcfg.json 70 | @echo 71 | 72 | reset: stop 73 | -$(NGSDN_TUTORIAL_SUDO) rm -rf ./tmp 74 | 75 | clean: 76 | -$(NGSDN_TUTORIAL_SUDO) rm -rf p4src/build 77 | -$(NGSDN_TUTORIAL_SUDO) rm -rf app/target 78 | -$(NGSDN_TUTORIAL_SUDO) rm -rf app/src/main/resources/bmv2.json 79 | -$(NGSDN_TUTORIAL_SUDO) rm -rf app/src/main/resources/p4info.txt 80 | 81 | p4-build: p4src/main.p4 82 | $(info *** Building P4 program...) 83 | @mkdir -p p4src/build 84 | docker run --rm -v ${curr_dir}:/workdir -w /workdir ${P4C_IMG} \ 85 | p4c-bm2-ss --arch v1model -o p4src/build/bmv2.json -DCPU_PORT=255 \ 86 | --p4runtime-files p4src/build/p4info.txt --Wdisable=unsupported \ 87 | p4src/main.p4 88 | @echo "*** P4 program compiled successfully! Output files are in p4src/build" 89 | 90 | _copy_p4c_out: 91 | $(info *** Copying p4c outputs to app resources...) 92 | @mkdir -p app/src/main/resources 93 | cp -f p4src/build/p4info.txt app/src/main/resources/ 94 | cp -f p4src/build/bmv2.json app/src/main/resources/ 95 | 96 | _mvn_package: 97 | $(info *** Building ONOS app...) 98 | @mkdir -p app/target 99 | @docker run --rm -v ${curr_dir}/app:/mvn-src -w /mvn-src ${MVN_IMG} mvn -o clean package 100 | 101 | app-build: p4-build _copy_p4c_out _mvn_package 102 | $(info *** ONOS app .oar package created succesfully) 103 | @ls -1 app/target/*.oar 104 | 105 | app-install: 106 | $(info *** Installing and activating app in ONOS...) 107 | ${onos_curl} -X POST -HContent-Type:application/octet-stream \ 108 | '${onos_url}/v1/applications?activate=true' \ 109 | --data-binary @app/target/srv6_usid-1.0-SNAPSHOT.oar 110 | @echo 111 | 112 | app-uninstall: 113 | $(info *** Uninstalling app from ONOS (if present)...) 114 | -${onos_curl} -X DELETE ${onos_url}/v1/applications/${app_name} 115 | @echo 116 | 117 | app-reload: app-uninstall app-install 118 | 119 | yang-tools: 120 | docker run --rm -it -v ${curr_dir}/yang/demo-port.yang:/models/demo-port.yang ${YANG_IMG} 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IPv6 Segment Routing SRv6
2 | SRv6 is a network architecture that encodes a list of instructions in the IPv6 packet header to define a network wide packet processing program.
3 | Each instruction defines a node to process the packet and the behavior to be applied to that packet by that node.
4 | The [SRv6 network programming](https://tools.ietf.org/html/draft-ietf-spring-srv6-network-programming-24) framework is being defined in IETF.
5 | 6 | # Implementation 7 | In the project we provide an open source data plane of SRv6 in P4. We Leverage the Open Network Operating System (ONOS) for the control plane.
8 | We augmented ONOS implementation with the necessary extensions to support SRv6.
9 | 10 | This work is done as part of the Research on Open SRv6 Ecosystem ([ROSE](https://netgroup.github.io/rose/)) project. 11 | 12 | This work is based on the P4 tutorial by the Open Networking Foundation. For more information about the above listed software modules you can visit the Open Networking Foundation original [repository](https://github.com/opennetworkinglab/ngsdn-tutorial).
13 | 14 | There you can also find useful material like the slides explaining the tutorial and a prepared Ubuntu virtual machine with all the software installed. It is strongly recommended to download the prepared VM and run the DEMO inside it, as it contains the several dependencies needed to run the software.
15 | 16 | 17 | # Repository structure 18 | This repository is structured as follows:
19 | * `app/` ONOS app Java implementation
20 | * `config/` configuration files
21 | * `mininet/` Mininet script to emulate a topology of `stratum_bmv2` devices
22 | * `p4src/` P4 implementation
23 | * `test/` test packets
24 | * `utils/` utilities include docker file
25 | 26 | ## Usage 27 | TBD 28 | 118 | -------------------------------------------------------------------------------- /app/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 4.0.0 21 | 22 | 23 | org.onosproject 24 | onos-dependencies 25 | 2.2.2 26 | 27 | 28 | org.onosproject 29 | srv6_usid 30 | 1.0-SNAPSHOT 31 | bundle 32 | 33 | Srv6 MicroSID app 34 | 35 | 36 | srv6_usid 37 | Srv6 MicroSID app 38 | https://www.onosproject.org 39 | Traffic Steering 40 | https://www.onosproject.org 41 | 42 | Srv6 MicroSID app 43 | 44 | 45 | 46 | 47 | 48 | org.onosproject 49 | onos-api 50 | ${onos.version} 51 | provided 52 | 53 | 54 | 55 | org.onosproject 56 | onos-protocols-p4runtime-model 57 | ${onos.version} 58 | provided 59 | 60 | 61 | 62 | org.onosproject 63 | onos-protocols-p4runtime-api 64 | ${onos.version} 65 | provided 66 | 67 | 68 | 69 | org.onosproject 70 | onos-protocols-grpc-api 71 | ${onos.version} 72 | provided 73 | 74 | 75 | 76 | org.onosproject 77 | onlab-osgi 78 | ${onos.version} 79 | provided 80 | 81 | 82 | 83 | org.onosproject 84 | onlab-misc 85 | ${onos.version} 86 | provided 87 | 88 | 89 | 90 | org.onosproject 91 | onos-cli 92 | ${onos.version} 93 | provided 94 | 95 | 96 | 97 | org.slf4j 98 | slf4j-api 99 | provided 100 | 101 | 102 | 103 | com.google.guava 104 | guava 105 | provided 106 | 107 | 108 | 109 | com.fasterxml.jackson.core 110 | jackson-databind 111 | provided 112 | 113 | 114 | 115 | junit 116 | junit 117 | test 118 | 119 | 120 | 121 | org.onosproject 122 | onos-api 123 | ${onos.version} 124 | test 125 | tests 126 | 127 | 128 | 129 | org.osgi 130 | org.osgi.service.component.annotations 131 | provided 132 | 133 | 134 | 135 | org.osgi 136 | org.osgi.core 137 | provided 138 | 139 | 140 | 141 | org.apache.karaf.shell 142 | org.apache.karaf.shell.console 143 | provided 144 | 145 | 146 | 147 | 148 | 149 | 150 | org.apache.felix 151 | maven-bundle-plugin 152 | 153 | 154 | 155 | org.onosproject.srv6_usid.cli 156 | 157 | 158 | 159 | 160 | 161 | org.onosproject 162 | onos-maven-plugin 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/AppConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.onosproject.srv6_usid; 18 | 19 | import org.onosproject.net.pi.model.PiPipeconfId; 20 | 21 | public class AppConstants { 22 | 23 | public static final String APP_NAME = "org.p4.srv6_usid"; 24 | public static final PiPipeconfId PIPECONF_ID = new PiPipeconfId("org.p4.srv6_usid"); 25 | 26 | public static final int DEFAULT_FLOW_RULE_PRIORITY = 10; 27 | public static final int INITIAL_SETUP_DELAY = 2; // Seconds. 28 | public static final int CLEAN_UP_DELAY = 2000; // milliseconds 29 | public static final int DEFAULT_CLEAN_UP_RETRY_TIMES = 10; 30 | 31 | public static final int CPU_PORT_ID = 255; 32 | public static final int CPU_CLONE_SESSION_ID = 99; 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/MainComponent.java: -------------------------------------------------------------------------------- 1 | package org.onosproject.srv6_usid; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.onlab.util.SharedScheduledExecutors; 5 | import org.onosproject.cfg.ComponentConfigService; 6 | import org.onosproject.core.ApplicationId; 7 | import org.onosproject.core.CoreService; 8 | import org.onosproject.net.Device; 9 | import org.onosproject.net.DeviceId; 10 | import org.onosproject.net.config.ConfigFactory; 11 | import org.onosproject.net.config.NetworkConfigRegistry; 12 | import org.onosproject.net.config.basics.SubjectFactories; 13 | import org.onosproject.net.device.DeviceService; 14 | import org.onosproject.net.flow.FlowRule; 15 | import org.onosproject.net.flow.FlowRuleService; 16 | import org.onosproject.net.group.Group; 17 | import org.onosproject.net.group.GroupService; 18 | import org.osgi.service.component.annotations.Activate; 19 | import org.osgi.service.component.annotations.Component; 20 | import org.osgi.service.component.annotations.Deactivate; 21 | import org.osgi.service.component.annotations.Reference; 22 | import org.osgi.service.component.annotations.ReferenceCardinality; 23 | import org.onosproject.srv6_usid.common.Srv6DeviceConfig; 24 | import org.onosproject.srv6_usid.pipeconf.PipeconfLoader; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | import java.util.Collection; 29 | import java.util.concurrent.ExecutorService; 30 | import java.util.concurrent.Executors; 31 | import java.util.concurrent.TimeUnit; 32 | 33 | import static org.onosproject.srv6_usid.AppConstants.APP_NAME; 34 | import static org.onosproject.srv6_usid.AppConstants.CLEAN_UP_DELAY; 35 | import static org.onosproject.srv6_usid.AppConstants.DEFAULT_CLEAN_UP_RETRY_TIMES; 36 | import static org.onosproject.srv6_usid.common.Utils.sleep; 37 | 38 | /** 39 | * A component which among other things registers the Srv6DeviceConfig to the 40 | * netcfg subsystem. 41 | */ 42 | @Component(immediate = true, service = MainComponent.class) 43 | public class MainComponent { 44 | 45 | private static final Logger log = 46 | LoggerFactory.getLogger(MainComponent.class.getName()); 47 | 48 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 49 | private CoreService coreService; 50 | 51 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 52 | //Force activation of this component after the pipeconf has been registered. 53 | @SuppressWarnings("unused") 54 | protected PipeconfLoader pipeconfLoader; 55 | 56 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 57 | protected NetworkConfigRegistry configRegistry; 58 | 59 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 60 | private GroupService groupService; 61 | 62 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 63 | private DeviceService deviceService; 64 | 65 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 66 | private FlowRuleService flowRuleService; 67 | 68 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 69 | private ComponentConfigService compCfgService; 70 | 71 | private final ConfigFactory srv6ConfigFactory = 72 | new ConfigFactory( 73 | SubjectFactories.DEVICE_SUBJECT_FACTORY, Srv6DeviceConfig.class, Srv6DeviceConfig.CONFIG_KEY) { 74 | @Override 75 | public Srv6DeviceConfig createConfig() { 76 | return new Srv6DeviceConfig(); 77 | } 78 | }; 79 | 80 | private ApplicationId appId; 81 | 82 | // For the sake of simplicity and to facilitate reading logs, use a 83 | // single-thread executor to serialize all configuration tasks. 84 | private final ExecutorService executorService = Executors.newSingleThreadExecutor(); 85 | 86 | @Activate 87 | protected void activate() { 88 | appId = coreService.registerApplication(APP_NAME); 89 | 90 | // Wait to remove flow and groups from previous executions. 91 | waitPreviousCleanup(); 92 | 93 | compCfgService.preSetProperty("org.onosproject.net.flow.impl.FlowRuleManager", 94 | "fallbackFlowPollFrequency", "4", false); 95 | compCfgService.preSetProperty("org.onosproject.net.group.impl.GroupManager", 96 | "fallbackGroupPollFrequency", "3", false); 97 | compCfgService.preSetProperty("org.onosproject.provider.host.impl.HostLocationProvider", 98 | "requestIpv6ND", "true", false); 99 | 100 | configRegistry.registerConfigFactory(srv6ConfigFactory); 101 | log.info("Started"); 102 | } 103 | 104 | @Deactivate 105 | protected void deactivate() { 106 | configRegistry.unregisterConfigFactory(srv6ConfigFactory); 107 | 108 | cleanUp(); 109 | 110 | log.info("Stopped"); 111 | } 112 | 113 | /** 114 | * Returns the application ID. 115 | * 116 | * @return application ID 117 | */ 118 | ApplicationId getAppId() { 119 | return appId; 120 | } 121 | 122 | /** 123 | * Returns the executor service managed by this component. 124 | * 125 | * @return executor service 126 | */ 127 | public ExecutorService getExecutorService() { 128 | return executorService; 129 | } 130 | 131 | /** 132 | * Schedules a task for the future using the executor service managed by 133 | * this component. 134 | * 135 | * @param task task runnable 136 | * @param delaySeconds delay in seconds 137 | */ 138 | public void scheduleTask(Runnable task, int delaySeconds) { 139 | SharedScheduledExecutors.newTimeout( 140 | () -> executorService.execute(task), 141 | delaySeconds, TimeUnit.SECONDS); 142 | } 143 | 144 | /** 145 | * Triggers clean up of flows and groups from this app, returns false if no 146 | * flows or groups were found, true otherwise. 147 | * 148 | * @return false if no flows or groups were found, true otherwise 149 | */ 150 | private boolean cleanUp() { 151 | Collection flows = Lists.newArrayList( 152 | flowRuleService.getFlowEntriesById(appId).iterator()); 153 | 154 | Collection groups = Lists.newArrayList(); 155 | for (Device device : deviceService.getAvailableDevices()) { 156 | groupService.getGroups(device.id(), appId).forEach(groups::add); 157 | } 158 | 159 | if (flows.isEmpty() && groups.isEmpty()) { 160 | return false; 161 | } 162 | 163 | flows.forEach(flowRuleService::removeFlowRules); 164 | if (!groups.isEmpty()) { 165 | // Wait for flows to be removed in case those depend on groups. 166 | sleep(1000); 167 | groups.forEach(g -> groupService.removeGroup( 168 | g.deviceId(), g.appCookie(), g.appId())); 169 | } 170 | 171 | return true; 172 | } 173 | 174 | private void waitPreviousCleanup() { 175 | int retry = DEFAULT_CLEAN_UP_RETRY_TIMES; 176 | while (retry != 0) { 177 | 178 | if (!cleanUp()) { 179 | return; 180 | } 181 | 182 | log.info("Waiting to remove flows and groups from " + 183 | "previous execution of {}...", 184 | appId.name()); 185 | 186 | sleep(CLEAN_UP_DELAY); 187 | 188 | --retry; 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/NdpReplyComponent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.onosproject.srv6_usid; 18 | 19 | import org.onlab.packet.Ip6Address; 20 | import org.onlab.packet.IpAddress; 21 | import org.onlab.packet.MacAddress; 22 | import org.onlab.util.ItemNotFoundException; 23 | import org.onosproject.core.ApplicationId; 24 | import org.onosproject.mastership.MastershipService; 25 | import org.onosproject.net.DeviceId; 26 | import org.onosproject.net.config.NetworkConfigService; 27 | import org.onosproject.net.device.DeviceEvent; 28 | import org.onosproject.net.device.DeviceListener; 29 | import org.onosproject.net.device.DeviceService; 30 | import org.onosproject.net.flow.DefaultFlowRule; 31 | import org.onosproject.net.flow.DefaultTrafficSelector; 32 | import org.onosproject.net.flow.DefaultTrafficTreatment; 33 | import org.onosproject.net.flow.FlowRule; 34 | import org.onosproject.net.flow.FlowRuleOperations; 35 | import org.onosproject.net.flow.FlowRuleService; 36 | import org.onosproject.net.flow.TrafficSelector; 37 | import org.onosproject.net.flow.TrafficTreatment; 38 | import org.onosproject.net.flow.criteria.PiCriterion; 39 | import org.onosproject.net.host.InterfaceIpAddress; 40 | import org.onosproject.net.intf.Interface; 41 | import org.onosproject.net.intf.InterfaceService; 42 | import org.onosproject.net.pi.model.PiActionId; 43 | import org.onosproject.net.pi.model.PiActionParamId; 44 | import org.onosproject.net.pi.model.PiMatchFieldId; 45 | import org.onosproject.net.pi.model.PiTableId; 46 | import org.onosproject.net.pi.runtime.PiAction; 47 | import org.onosproject.net.pi.runtime.PiActionParam; 48 | import org.osgi.service.component.annotations.Activate; 49 | import org.osgi.service.component.annotations.Component; 50 | import org.osgi.service.component.annotations.Deactivate; 51 | import org.osgi.service.component.annotations.Reference; 52 | import org.osgi.service.component.annotations.ReferenceCardinality; 53 | import org.onosproject.srv6_usid.common.Srv6DeviceConfig; 54 | import org.slf4j.Logger; 55 | import org.slf4j.LoggerFactory; 56 | 57 | import java.util.Collection; 58 | import java.util.stream.Collectors; 59 | 60 | import static org.onosproject.srv6_usid.AppConstants.DEFAULT_FLOW_RULE_PRIORITY; 61 | import static org.onosproject.srv6_usid.AppConstants.INITIAL_SETUP_DELAY; 62 | 63 | /** 64 | * App component that configures devices to generate NDP Neighbor Advertisement 65 | * packets for all interface IPv6 addresses configured in the netcfg. 66 | */ 67 | @Component( 68 | immediate = true, 69 | enabled = true 70 | ) 71 | public class NdpReplyComponent { 72 | 73 | private static final Logger log = 74 | LoggerFactory.getLogger(NdpReplyComponent.class.getName()); 75 | 76 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 77 | protected NetworkConfigService configService; 78 | 79 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 80 | protected FlowRuleService flowRuleService; 81 | 82 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 83 | protected InterfaceService interfaceService; 84 | 85 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 86 | protected MastershipService mastershipService; 87 | 88 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 89 | protected DeviceService deviceService; 90 | 91 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 92 | private MainComponent mainComponent; 93 | 94 | private DeviceListener deviceListener = new InternalDeviceListener(); 95 | private ApplicationId appId; 96 | 97 | @Activate 98 | public void activate() { 99 | appId = mainComponent.getAppId(); 100 | 101 | deviceService.addListener(deviceListener); 102 | 103 | mainComponent.scheduleTask(this::setUpAllDevices, INITIAL_SETUP_DELAY); 104 | 105 | log.info("Started"); 106 | } 107 | 108 | @Deactivate 109 | public void deactivate() { 110 | deviceService.removeListener(deviceListener); 111 | 112 | log.info("Stopped"); 113 | } 114 | 115 | private void setUpAllDevices() { 116 | deviceService.getAvailableDevices().forEach(device -> { 117 | if (mastershipService.isLocalMaster(device.id())) { 118 | log.info("*** NDP REPLY - Starting Initial set up for {}...", device.id()); 119 | setUpDevice(device.id()); 120 | } 121 | }); 122 | } 123 | 124 | private void setUpDevice(DeviceId deviceId) { 125 | Srv6DeviceConfig config = configService.getConfig(deviceId, Srv6DeviceConfig.class); 126 | if (config == null) { 127 | // Config not available yet 128 | throw new ItemNotFoundException("Missing Srv6Config for " + deviceId); 129 | } 130 | 131 | final MacAddress deviceMac = config.myStationMac(); 132 | 133 | // Get all interface for the device 134 | final Collection interfaces = interfaceService.getInterfaces() 135 | .stream() 136 | .filter(iface -> iface.connectPoint().deviceId().equals(deviceId)) 137 | .collect(Collectors.toSet()); 138 | 139 | if (interfaces.isEmpty()) { 140 | log.info("{} does not have any IPv6 interface configured", 141 | deviceId); 142 | return; 143 | } 144 | 145 | log.info("Adding rules to {} to generate NDP NA for {} IPv6 interfaces...", 146 | deviceId, interfaces.size()); 147 | 148 | final Collection flowRules = interfaces.stream() 149 | .map(this::getIp6Addresses) 150 | .flatMap(Collection::stream) 151 | .map(iaddr -> buildNdpReplyFlowRule(deviceId, deviceMac, iaddr)) 152 | .collect(Collectors.toSet()); 153 | 154 | installRules(flowRules); 155 | } 156 | 157 | private Collection getIp6Addresses(Interface iface) { 158 | return iface.ipAddressesList() 159 | .stream() 160 | .map(InterfaceIpAddress::ipAddress) 161 | .filter(IpAddress::isIp6) 162 | .map(IpAddress::getIp6Address) 163 | .collect(Collectors.toSet()); 164 | } 165 | 166 | private void installRules(Collection flowRules) { 167 | FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); 168 | flowRules.forEach(ops::add); 169 | flowRuleService.apply(ops.build()); 170 | } 171 | 172 | private FlowRule buildNdpReplyFlowRule(DeviceId deviceId, 173 | MacAddress deviceMac, 174 | Ip6Address targetIp) { 175 | PiCriterion match = PiCriterion.builder() 176 | .matchExact(PiMatchFieldId.of("hdr.ndp.target_addr"), targetIp.toOctets()) 177 | .build(); 178 | 179 | PiActionParam paramRouterMac = new PiActionParam( 180 | PiActionParamId.of("target_mac"), deviceMac.toBytes()); 181 | PiAction action = PiAction.builder() 182 | .withId(PiActionId.of("IngressPipeImpl.ndp_ns_to_na")) 183 | .withParameter(paramRouterMac) 184 | .build(); 185 | 186 | TrafficSelector selector = DefaultTrafficSelector.builder() 187 | .matchPi(match) 188 | .build(); 189 | 190 | TrafficTreatment treatment = DefaultTrafficTreatment.builder() 191 | .piTableAction(action) 192 | .build(); 193 | 194 | return DefaultFlowRule.builder() 195 | .forDevice(deviceId) 196 | .forTable(PiTableId.of("IngressPipeImpl.ndp_reply_table")) 197 | .fromApp(appId) 198 | .makePermanent() 199 | .withSelector(selector) 200 | .withTreatment(treatment) 201 | .withPriority(DEFAULT_FLOW_RULE_PRIORITY) 202 | .build(); 203 | } 204 | 205 | /** 206 | * Listener of device events. 207 | */ 208 | public class InternalDeviceListener implements DeviceListener { 209 | 210 | @Override 211 | public boolean isRelevant(DeviceEvent event) { 212 | switch (event.type()) { 213 | case DEVICE_ADDED: 214 | case DEVICE_AVAILABILITY_CHANGED: 215 | break; 216 | default: 217 | // Ignore other events. 218 | return false; 219 | } 220 | // Process only if this controller instance is the master. 221 | final DeviceId deviceId = event.subject().id(); 222 | return mastershipService.isLocalMaster(deviceId); 223 | } 224 | 225 | @Override 226 | public void event(DeviceEvent event) { 227 | final DeviceId deviceId = event.subject().id(); 228 | if (deviceService.isAvailable(deviceId)) { 229 | // A P4Runtime device is considered available in ONOS when there 230 | // is a StreamChannel session open and the pipeline 231 | // configuration has been set. 232 | mainComponent.getExecutorService().execute(() -> { 233 | log.info("{} event! deviceId={}", event.type(), deviceId); 234 | 235 | setUpDevice(deviceId); 236 | }); 237 | } 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/Srv6Component.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.onosproject.srv6_usid; 17 | 18 | import com.google.common.collect.Lists; 19 | import org.onlab.packet.Ip6Address; 20 | import org.onosproject.core.ApplicationId; 21 | import org.onosproject.mastership.MastershipService; 22 | import org.onosproject.net.Device; 23 | import org.onosproject.net.DeviceId; 24 | import org.onosproject.net.config.NetworkConfigService; 25 | import org.onosproject.net.device.DeviceEvent; 26 | import org.onosproject.net.device.DeviceListener; 27 | import org.onosproject.net.device.DeviceService; 28 | import org.onosproject.net.flow.FlowRule; 29 | import org.onosproject.net.flow.FlowRuleOperations; 30 | import org.onosproject.net.flow.FlowRuleService; 31 | import org.onosproject.net.flow.criteria.PiCriterion; 32 | import org.onosproject.net.pi.model.PiActionId; 33 | import org.onosproject.net.pi.model.PiActionParamId; 34 | import org.onosproject.net.pi.model.PiMatchFieldId; 35 | import org.onosproject.net.pi.model.PiTableId; 36 | import org.onosproject.net.pi.runtime.PiAction; 37 | import org.onosproject.net.pi.runtime.PiActionParam; 38 | import org.onosproject.net.pi.runtime.PiTableAction; 39 | import org.osgi.service.component.annotations.Activate; 40 | import org.osgi.service.component.annotations.Component; 41 | import org.osgi.service.component.annotations.Deactivate; 42 | import org.osgi.service.component.annotations.Reference; 43 | import org.osgi.service.component.annotations.ReferenceCardinality; 44 | import org.onosproject.srv6_usid.common.Srv6DeviceConfig; 45 | import org.onosproject.srv6_usid.common.Utils; 46 | import org.slf4j.Logger; 47 | import org.slf4j.LoggerFactory; 48 | import org.onlab.packet.MacAddress; 49 | 50 | 51 | import java.util.List; 52 | import java.util.Optional; 53 | 54 | import static com.google.common.collect.Streams.stream; 55 | import static org.onosproject.srv6_usid.AppConstants.INITIAL_SETUP_DELAY; 56 | 57 | /** 58 | * Application which handles SRv6 segment routing. 59 | */ 60 | @Component( 61 | immediate = true, 62 | enabled = true, 63 | service = Srv6Component.class 64 | ) 65 | public class Srv6Component { 66 | 67 | private static final Logger log = LoggerFactory.getLogger(Srv6Component.class); 68 | 69 | //-------------------------------------------------------------------------- 70 | // ONOS CORE SERVICE BINDING 71 | // 72 | // These variables are set by the Karaf runtime environment before calling 73 | // the activate() method. 74 | //-------------------------------------------------------------------------- 75 | 76 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 77 | private FlowRuleService flowRuleService; 78 | 79 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 80 | private MastershipService mastershipService; 81 | 82 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 83 | private DeviceService deviceService; 84 | 85 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 86 | private NetworkConfigService networkConfigService; 87 | 88 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 89 | private MainComponent mainComponent; 90 | 91 | private final DeviceListener deviceListener = new Srv6Component.InternalDeviceListener(); 92 | 93 | private ApplicationId appId; 94 | 95 | //-------------------------------------------------------------------------- 96 | // COMPONENT ACTIVATION. 97 | // 98 | // When loading/unloading the app the Karaf runtime environment will call 99 | // activate()/deactivate(). 100 | //-------------------------------------------------------------------------- 101 | 102 | @Activate 103 | protected void activate() { 104 | appId = mainComponent.getAppId(); 105 | 106 | // Register listeners to be informed about device and host events. 107 | deviceService.addListener(deviceListener); 108 | 109 | // Schedule set up for all devices. 110 | mainComponent.scheduleTask(this::setUpAllDevices, INITIAL_SETUP_DELAY); 111 | 112 | log.info("Started"); 113 | } 114 | 115 | @Deactivate 116 | protected void deactivate() { 117 | deviceService.removeListener(deviceListener); 118 | 119 | log.info("Stopped"); 120 | } 121 | 122 | /** 123 | * Populate the My micro SID table from the network configuration for the 124 | * specified device. 125 | * 126 | * @param deviceId the device Id 127 | */ 128 | private void setUpMyUSidTable(DeviceId deviceId) { 129 | //TODO: add to the listenet 130 | Ip6Address myUSid = getMyUSid(deviceId); 131 | Ip6Address myUDX = getMyUDX(deviceId); 132 | 133 | log.info("Adding two myUSid rules on {} (sid {})...", deviceId, myUSid); 134 | 135 | String tableId = "IngressPipeImpl.srv6_localsid_table"; 136 | 137 | PiCriterion match = PiCriterion.builder() 138 | .matchLpm( 139 | PiMatchFieldId.of("hdr.ipv6.dst_addr"), 140 | myUSid.toOctets(), 48) 141 | .build(); 142 | 143 | PiTableAction action = PiAction.builder() 144 | .withId(PiActionId.of("IngressPipeImpl.srv6_usid_un")) 145 | .build(); 146 | 147 | FlowRule myStationRule = Utils.buildFlowRule( 148 | deviceId, appId, tableId, match, action); 149 | 150 | flowRuleService.applyFlowRules(myStationRule); 151 | 152 | match = PiCriterion.builder() 153 | .matchLpm( 154 | PiMatchFieldId.of("hdr.ipv6.dst_addr"), 155 | myUSid.toOctets(), 64) 156 | .build(); 157 | action = PiAction.builder() 158 | .withId(PiActionId.of("IngressPipeImpl.srv6_end")) 159 | .build(); 160 | myStationRule = Utils.buildFlowRule( 161 | deviceId, appId, tableId, match, action); 162 | flowRuleService.applyFlowRules(myStationRule); 163 | 164 | if (myUDX != null) { 165 | match = PiCriterion.builder() 166 | .matchLpm( 167 | PiMatchFieldId.of("hdr.ipv6.dst_addr"), 168 | myUDX.toOctets(), 64) 169 | .build(); 170 | action = PiAction.builder() 171 | .withId(PiActionId.of("IngressPipeImpl.srv6_end_dx6")) 172 | .build(); 173 | myStationRule = Utils.buildFlowRule( 174 | deviceId, appId, tableId, match, action); 175 | flowRuleService.applyFlowRules(myStationRule); 176 | } 177 | } 178 | 179 | /* 180 | * Insert a uA instruction 181 | */ 182 | public void insertUARule(DeviceId routerId, Ip6Address uAInstruction, 183 | Ip6Address nextHopIpv6, MacAddress nextHopMac) { 184 | log.info("Adding a uAInstruction on {}...", routerId); 185 | 186 | final String uATableId = "IngressPipeImpl.srv6_localsid_table"; 187 | final String uAActionName = "IngressPipeImpl.srv6_usid_ua"; 188 | 189 | final String xconnTableId = "IngressPipeImpl.xconnect_table"; 190 | final String xconnActionName = "IngressPipeImpl.xconnect_act"; 191 | 192 | final int mask = 64; 193 | 194 | PiCriterion match = PiCriterion.builder() 195 | .matchLpm( 196 | PiMatchFieldId.of("hdr.ipv6.dst_addr"), 197 | uAInstruction.toOctets(), 198 | mask) 199 | .build(); 200 | 201 | PiAction action = PiAction.builder() 202 | .withId(PiActionId.of(uAActionName)) 203 | .withParameter(new PiActionParam( 204 | // Action param name. 205 | PiActionParamId.of("next_hop"), 206 | // Action param value. 207 | nextHopIpv6.toOctets())) 208 | .build(); 209 | 210 | flowRuleService.applyFlowRules(Utils 211 | .buildFlowRule(routerId, appId, uATableId, match, action)); 212 | 213 | 214 | match = PiCriterion.builder() 215 | .matchLpm( 216 | PiMatchFieldId.of("local_metadata.ua_next_hop"), 217 | nextHopIpv6.toOctets(), 218 | mask) 219 | .build(); 220 | 221 | action = PiAction.builder() 222 | .withId(PiActionId.of(xconnActionName)) 223 | .withParameter(new PiActionParam( 224 | PiActionParamId.of("next_hop"), 225 | nextHopMac.toBytes())) 226 | .build(); 227 | 228 | flowRuleService.applyFlowRules(Utils 229 | .buildFlowRule(routerId, appId, xconnTableId, match, action)); 230 | } 231 | 232 | 233 | 234 | /** 235 | * Insert a micro SID encap insert policy that will inject an IPv6 in IPv6 header for 236 | * packets destined to destIp. 237 | * 238 | * @param deviceId device ID 239 | * @param destIp target IP address for the SRv6 policy 240 | * @param prefixLength prefix length for the target IP 241 | * @param segmentList list of SRv6 SIDs that make up the path 242 | */ 243 | public void insertSrv6InsertRule(DeviceId deviceId, Ip6Address destIp, int prefixLength, 244 | List segmentList) { 245 | 246 | String tableId = "IngressPipeImpl.srv6_encap"; 247 | Ip6Address myUSid= getMyUSid(deviceId); 248 | 249 | PiCriterion match = PiCriterion.builder() 250 | .matchLpm(PiMatchFieldId.of("hdr.ipv6.dst_addr"), destIp.toOctets(), prefixLength) 251 | .build(); 252 | 253 | List actionParams = Lists.newArrayList(); 254 | 255 | PiActionParamId paramId = PiActionParamId.of("src_addr"); 256 | PiActionParam param = new PiActionParam(paramId, myUSid.toOctets()); 257 | actionParams.add(param); 258 | 259 | for (int i = 0; i < segmentList.size()-1; i++) { 260 | paramId = PiActionParamId.of("s" + (i + 1)); 261 | param = new PiActionParam(paramId, segmentList.get(i).toOctets()); 262 | actionParams.add(param); 263 | } 264 | 265 | PiAction action = PiAction.builder() 266 | .withId(PiActionId.of("IngressPipeImpl.usid_encap_" + (segmentList.size() - 1))) 267 | .withParameters(actionParams) 268 | .build(); 269 | 270 | final FlowRule rule = Utils.buildFlowRule( 271 | deviceId, appId, tableId, match, action); 272 | 273 | flowRuleService.applyFlowRules(rule); 274 | } 275 | 276 | /** 277 | * Remove all SRv6 transit insert polices for the specified device. 278 | * 279 | * @param deviceId device ID 280 | */ 281 | public void clearSrv6InsertRules(DeviceId deviceId) { 282 | String tableId = "IngressPipeImpl.srv6_encap"; 283 | 284 | FlowRuleOperations.Builder ops = FlowRuleOperations.builder(); 285 | stream(flowRuleService.getFlowEntries(deviceId)) 286 | .filter(fe -> fe.appId() == appId.id()) 287 | .filter(fe -> fe.table().equals(PiTableId.of(tableId))) 288 | .forEach(ops::remove); 289 | flowRuleService.apply(ops.build()); 290 | } 291 | 292 | // ---------- END METHODS TO COMPLETE ---------------- 293 | 294 | //-------------------------------------------------------------------------- 295 | // EVENT LISTENERS 296 | // 297 | // Events are processed only if isRelevant() returns true. 298 | //-------------------------------------------------------------------------- 299 | 300 | /** 301 | * Listener of device events. 302 | */ 303 | public class InternalDeviceListener implements DeviceListener { 304 | 305 | @Override 306 | public boolean isRelevant(DeviceEvent event) { 307 | switch (event.type()) { 308 | case DEVICE_ADDED: 309 | case DEVICE_AVAILABILITY_CHANGED: 310 | break; 311 | default: 312 | // Ignore other events. 313 | return false; 314 | } 315 | // Process only if this controller instance is the master. 316 | final DeviceId deviceId = event.subject().id(); 317 | return mastershipService.isLocalMaster(deviceId); 318 | } 319 | 320 | @Override 321 | public void event(DeviceEvent event) { 322 | final DeviceId deviceId = event.subject().id(); 323 | if (deviceService.isAvailable(deviceId)) { 324 | // A P4Runtime device is considered available in ONOS when there 325 | // is a StreamChannel session open and the pipeline 326 | // configuration has been set. 327 | mainComponent.getExecutorService().execute(() -> { 328 | log.info("{} event! deviceId={}", event.type(), deviceId); 329 | 330 | setUpMyUSidTable(event.subject().id()); 331 | }); 332 | } 333 | } 334 | } 335 | 336 | 337 | //-------------------------------------------------------------------------- 338 | // UTILITY METHODS 339 | //-------------------------------------------------------------------------- 340 | 341 | /** 342 | * Sets up SRv6 My SID table on all devices known by ONOS and for which this 343 | * ONOS node instance is currently master. 344 | */ 345 | private synchronized void setUpAllDevices() { 346 | // Set up host routes 347 | stream(deviceService.getAvailableDevices()) 348 | .map(Device::id) 349 | .filter(mastershipService::isLocalMaster) 350 | .forEach(deviceId -> { 351 | log.info("*** SRV6 - Starting initial set up for {}...", deviceId); 352 | this.setUpMyUSidTable(deviceId); 353 | }); 354 | } 355 | 356 | /** 357 | * Returns the Srv6 config for the given device. 358 | * 359 | * @param deviceId the device ID 360 | * @return Srv6 device config 361 | */ 362 | private Optional getDeviceConfig(DeviceId deviceId) { 363 | Srv6DeviceConfig config = networkConfigService.getConfig(deviceId, Srv6DeviceConfig.class); 364 | return Optional.ofNullable(config); 365 | } 366 | 367 | /** 368 | * Returns Srv6 uSID for the given device. 369 | * 370 | * @param deviceId the device ID 371 | * @return SID for the device 372 | */ 373 | private Ip6Address getMyUSid(DeviceId deviceId) { 374 | return getDeviceConfig(deviceId) 375 | .map(Srv6DeviceConfig::myUSid) 376 | .orElseThrow(() -> new RuntimeException( 377 | "Missing myUSid config for " + deviceId)); 378 | } 379 | 380 | /** 381 | * Returns Srv6 uDX for the given device. 382 | * 383 | * @param deviceId the device ID 384 | * @return uDX for the device 385 | */ 386 | private Ip6Address getMyUDX(DeviceId deviceId) { 387 | return getDeviceConfig(deviceId) 388 | .map(Srv6DeviceConfig::myUDX) 389 | .orElse(null); 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/cli/RouteInsertCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.onosproject.srv6_usid.cli; 17 | 18 | import org.apache.karaf.shell.api.action.Argument; 19 | import org.apache.karaf.shell.api.action.Command; 20 | import org.apache.karaf.shell.api.action.Completion; 21 | import org.apache.karaf.shell.api.action.lifecycle.Service; 22 | import org.onlab.packet.Ip6Address; 23 | import org.onlab.packet.IpAddress; 24 | import org.onosproject.cli.AbstractShellCommand; 25 | import org.onosproject.cli.net.DeviceIdCompleter; 26 | import org.onosproject.net.Device; 27 | import org.onosproject.net.DeviceId; 28 | import org.onosproject.net.device.DeviceService; 29 | import org.onlab.packet.MacAddress; 30 | import org.onosproject.srv6_usid.Ipv6RoutingComponent; 31 | 32 | /** 33 | * Ipv6 Route Insert Command 34 | */ 35 | @Service 36 | @Command(scope = "onos", name = "route-insert", 37 | description = "Insert a t_insert rule into the IPv6 Routing table") 38 | public class RouteInsertCommand extends AbstractShellCommand { 39 | 40 | @Argument(index = 0, name = "uri", description = "Device ID", 41 | required = true, multiValued = false) 42 | @Completion(DeviceIdCompleter.class) 43 | String uri = null; 44 | 45 | @Argument(index = 1, name = "ipv6NetAddress", 46 | description = "IPv6 address", 47 | required = true, multiValued = false) 48 | String ipv6NetAddr = null; 49 | 50 | @Argument(index = 2, name = "mask", 51 | description = "IPv6 mask", 52 | required = false, multiValued = false) 53 | int mask = 64; 54 | 55 | @Argument(index = 3, name = "macDstAddr", 56 | description = "MAC destination address", 57 | required = true, multiValued = false) 58 | String macDstAddr = null; 59 | 60 | @Override 61 | protected void doExecute() { 62 | DeviceService deviceService = get(DeviceService.class); 63 | Ipv6RoutingComponent app = get(Ipv6RoutingComponent.class); 64 | 65 | Device device = deviceService.getDevice(DeviceId.deviceId(uri)); 66 | if (device == null) { 67 | print("Device \"%s\" is not found", uri); 68 | return; 69 | } 70 | 71 | Ip6Address destIp = Ip6Address.valueOf(ipv6NetAddr); 72 | MacAddress nextHop = MacAddress.valueOf(macDstAddr); 73 | 74 | print("Installing route on device %s", uri); 75 | 76 | app.insertRoutingRule(device.id(), destIp, mask, nextHop); 77 | 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/cli/Srv6ClearCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.onosproject.srv6_usid.cli; 17 | 18 | import org.apache.karaf.shell.api.action.Argument; 19 | import org.apache.karaf.shell.api.action.Command; 20 | import org.apache.karaf.shell.api.action.Completion; 21 | import org.apache.karaf.shell.api.action.lifecycle.Service; 22 | import org.onosproject.cli.AbstractShellCommand; 23 | import org.onosproject.cli.net.DeviceIdCompleter; 24 | import org.onosproject.net.Device; 25 | import org.onosproject.net.DeviceId; 26 | import org.onosproject.net.device.DeviceService; 27 | import org.onosproject.srv6_usid.Srv6Component; 28 | 29 | /** 30 | * SRv6 Transit Clear Command 31 | */ 32 | @Service 33 | @Command(scope = "onos", name = "srv6-clear", 34 | description = "Clears all t_insert rules from the SRv6 Transit table") 35 | public class Srv6ClearCommand extends AbstractShellCommand { 36 | 37 | @Argument(index = 0, name = "uri", description = "Device ID", 38 | required = true, multiValued = false) 39 | @Completion(DeviceIdCompleter.class) 40 | String uri = null; 41 | 42 | @Override 43 | protected void doExecute() { 44 | DeviceService deviceService = get(DeviceService.class); 45 | Srv6Component app = get(Srv6Component.class); 46 | 47 | Device device = deviceService.getDevice(DeviceId.deviceId(uri)); 48 | if (device == null) { 49 | print("Device \"%s\" is not found", uri); 50 | return; 51 | } 52 | app.clearSrv6InsertRules(device.id()); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/cli/Srv6InsertCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.onosproject.srv6_usid.cli; 17 | 18 | import org.apache.karaf.shell.api.action.Argument; 19 | import org.apache.karaf.shell.api.action.Command; 20 | import org.apache.karaf.shell.api.action.Completion; 21 | import org.apache.karaf.shell.api.action.lifecycle.Service; 22 | import org.onlab.packet.Ip6Address; 23 | import org.onlab.packet.IpAddress; 24 | import org.onosproject.cli.AbstractShellCommand; 25 | import org.onosproject.cli.net.DeviceIdCompleter; 26 | import org.onosproject.net.Device; 27 | import org.onosproject.net.DeviceId; 28 | import org.onosproject.net.device.DeviceService; 29 | import org.onosproject.srv6_usid.Srv6Component; 30 | 31 | import java.util.List; 32 | import java.util.stream.Collectors; 33 | 34 | /** 35 | * SRv6 Transit Insert Command 36 | */ 37 | @Service 38 | @Command(scope = "onos", name = "srv6-insert", 39 | description = "Insert a t_insert rule into the SRv6 Transit table") 40 | public class Srv6InsertCommand extends AbstractShellCommand { 41 | 42 | @Argument(index = 0, name = "uri", description = "Device ID", 43 | required = true, multiValued = false) 44 | @Completion(DeviceIdCompleter.class) 45 | String uri = null; 46 | 47 | @Argument(index = 1, name = "segments", 48 | description = "SRv6 Segments (space separated list); last segment is target IP address", 49 | required = false, multiValued = true) 50 | @Completion(Srv6SidCompleter.class) 51 | List segments = null; 52 | 53 | @Override 54 | protected void doExecute() { 55 | DeviceService deviceService = get(DeviceService.class); 56 | Srv6Component app = get(Srv6Component.class); 57 | 58 | Device device = deviceService.getDevice(DeviceId.deviceId(uri)); 59 | if (device == null) { 60 | print("Device \"%s\" is not found", uri); 61 | return; 62 | } 63 | if (segments.size() == 0) { 64 | print("No segments listed"); 65 | return; 66 | } 67 | List sids = segments.stream() 68 | .map(Ip6Address::valueOf) 69 | .collect(Collectors.toList()); 70 | Ip6Address destIp = sids.get(sids.size() - 1); 71 | 72 | print("Installing path on device %s: %s", 73 | uri, sids.stream() 74 | .map(IpAddress::toString) 75 | .collect(Collectors.joining(", "))); 76 | app.insertSrv6InsertRule(device.id(), destIp, 128, sids); 77 | 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/cli/Srv6SidCompleter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.onosproject.srv6_usid.cli; 17 | 18 | import org.apache.karaf.shell.api.action.lifecycle.Service; 19 | import org.apache.karaf.shell.api.console.CommandLine; 20 | import org.apache.karaf.shell.api.console.Completer; 21 | import org.apache.karaf.shell.api.console.Session; 22 | import org.apache.karaf.shell.support.completers.StringsCompleter; 23 | import org.onosproject.cli.AbstractShellCommand; 24 | import org.onosproject.net.config.NetworkConfigService; 25 | import org.onosproject.net.device.DeviceService; 26 | import org.onosproject.srv6_usid.common.Srv6DeviceConfig; 27 | 28 | import java.util.List; 29 | import java.util.Objects; 30 | import java.util.SortedSet; 31 | 32 | import static com.google.common.collect.Streams.stream; 33 | 34 | /** 35 | * Completer for SIDs based on device config. 36 | */ 37 | @Service 38 | public class Srv6SidCompleter implements Completer { 39 | 40 | @Override 41 | public int complete(Session session, CommandLine commandLine, List candidates) { 42 | DeviceService deviceService = AbstractShellCommand.get(DeviceService.class); 43 | NetworkConfigService netCfgService = AbstractShellCommand.get(NetworkConfigService.class); 44 | 45 | // Delegate string completer 46 | StringsCompleter delegate = new StringsCompleter(); 47 | SortedSet strings = delegate.getStrings(); 48 | 49 | stream(deviceService.getDevices()) 50 | .map(d -> netCfgService.getConfig(d.id(), Srv6DeviceConfig.class)) 51 | .filter(Objects::nonNull) 52 | .map(Srv6DeviceConfig::myUSid) 53 | .filter(Objects::nonNull) 54 | .forEach(sid -> strings.add(sid.toString())); 55 | 56 | // Now let the completer do the work for figuring out what to offer. 57 | return delegate.complete(session, commandLine, candidates); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/cli/UAInsertCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.onosproject.srv6_usid.cli; 17 | 18 | import org.apache.karaf.shell.api.action.Argument; 19 | import org.apache.karaf.shell.api.action.Command; 20 | import org.apache.karaf.shell.api.action.Completion; 21 | import org.apache.karaf.shell.api.action.lifecycle.Service; 22 | import org.onlab.packet.Ip6Address; 23 | import org.onlab.packet.IpAddress; 24 | import org.onosproject.cli.AbstractShellCommand; 25 | import org.onosproject.cli.net.DeviceIdCompleter; 26 | import org.onosproject.net.Device; 27 | import org.onosproject.net.DeviceId; 28 | import org.onosproject.net.device.DeviceService; 29 | import org.onlab.packet.MacAddress; 30 | import org.onosproject.srv6_usid.Srv6Component; 31 | 32 | /** 33 | * uA Insert Command 34 | */ 35 | @Service 36 | @Command(scope = "onos", name = "uA-insert", 37 | description = "Insert a uA rule into the IPv6 Routing table and xconnect table") 38 | public class UAInsertCommand extends AbstractShellCommand { 39 | 40 | @Argument(index = 0, name = "uri", description = "Device ID", 41 | required = true, multiValued = false) 42 | @Completion(DeviceIdCompleter.class) 43 | String uri = null; 44 | 45 | @Argument(index = 1, name = "uAInstruction", 46 | description = "IPv6 address of the uA Instruction", 47 | required = true, multiValued = false) 48 | String uAInstruction = null; 49 | 50 | @Argument(index = 2, name = "ipv6NextHop", 51 | description = "IPv6 address", 52 | required = true, multiValued = false) 53 | String ipv6NextHop = null; 54 | 55 | @Argument(index = 3, name = "macDstAddr", 56 | description = "MAC destination address", 57 | required = true, multiValued = false) 58 | String macDstAddr = null; 59 | 60 | @Override 61 | protected void doExecute() { 62 | DeviceService deviceService = get(DeviceService.class); 63 | Srv6Component app = get(Srv6Component.class); 64 | 65 | Device device = deviceService.getDevice(DeviceId.deviceId(uri)); 66 | if (device == null) { 67 | print("Device \"%s\" is not found", uri); 68 | return; 69 | } 70 | 71 | Ip6Address uAInst = Ip6Address.valueOf(uAInstruction); 72 | Ip6Address nextHop = Ip6Address.valueOf(ipv6NextHop); 73 | MacAddress nextHopMac = MacAddress.valueOf(macDstAddr); 74 | 75 | print("Installing uA Instruction on device %s", uri); 76 | 77 | app.insertUARule(device.id(), uAInst, nextHop, nextHopMac); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/common/Srv6DeviceConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.onosproject.srv6_usid.common; 18 | 19 | import org.onlab.packet.Ip6Address; 20 | import org.onlab.packet.MacAddress; 21 | import org.onosproject.net.DeviceId; 22 | import org.onosproject.net.config.Config; 23 | 24 | /** 25 | * Device configuration object for the SRv6 srv6_usid application. 26 | */ 27 | public class Srv6DeviceConfig extends Config { 28 | 29 | public static final String CONFIG_KEY = "srv6DeviceConfig"; 30 | private static final String MY_STATION_MAC = "myStationMac"; 31 | private static final String MY_USID = "uN"; 32 | private static final String MY_UDX = "uDX"; 33 | private static final String IS_CORE = "isCore"; 34 | 35 | @Override 36 | public boolean isValid() { 37 | return myStationMac() != null && 38 | myUSid() != null; 39 | } 40 | 41 | /** 42 | * Gets the MAC address of the switch. 43 | * 44 | * @return MAC address of the switch. Or null if not configured. 45 | */ 46 | public MacAddress myStationMac() { 47 | String mac = get(MY_STATION_MAC, null); 48 | return mac != null ? MacAddress.valueOf(mac) : null; 49 | } 50 | 51 | /** 52 | * Gets the SRv6 micro segment ID (uSID) of the switch. 53 | * 54 | * @return IP microSID address of the router. Or null if not configured. 55 | */ 56 | public Ip6Address myUSid() { 57 | String ip = get(MY_USID, null); 58 | return ip != null ? Ip6Address.valueOf(ip) : null; 59 | } 60 | 61 | /** 62 | * Gets the SRv6 uDX instruction of the switch. 63 | * 64 | * @return uDX instruction of the router. Or null if not configured. 65 | */ 66 | public Ip6Address myUDX() { 67 | String uDX = get(MY_UDX, null); 68 | return uDX != null ? Ip6Address.valueOf(uDX) : null; 69 | } 70 | 71 | /** 72 | * Checks if the switch is a core switch. 73 | * 74 | * @return true if the switch is a core switch. false if the switch is not 75 | * a core switch, or if the value is not configured. 76 | */ 77 | public boolean isCore() { 78 | String isCore = get(IS_CORE, null); 79 | return isCore != null && Boolean.valueOf(isCore); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/common/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.onosproject.srv6_usid.common; 18 | 19 | import org.onosproject.core.ApplicationId; 20 | import org.onosproject.net.DeviceId; 21 | import org.onosproject.net.PortNumber; 22 | import org.onosproject.net.flow.DefaultFlowRule; 23 | import org.onosproject.net.flow.DefaultTrafficSelector; 24 | import org.onosproject.net.flow.DefaultTrafficTreatment; 25 | import org.onosproject.net.flow.FlowRule; 26 | import org.onosproject.net.flow.criteria.PiCriterion; 27 | import org.onosproject.net.group.DefaultGroupBucket; 28 | import org.onosproject.net.group.DefaultGroupDescription; 29 | import org.onosproject.net.group.DefaultGroupKey; 30 | import org.onosproject.net.group.GroupBucket; 31 | import org.onosproject.net.group.GroupBuckets; 32 | import org.onosproject.net.group.GroupDescription; 33 | import org.onosproject.net.group.GroupKey; 34 | import org.onosproject.net.pi.model.PiActionProfileId; 35 | import org.onosproject.net.pi.model.PiTableId; 36 | import org.onosproject.net.pi.runtime.PiAction; 37 | import org.onosproject.net.pi.runtime.PiGroupKey; 38 | import org.onosproject.net.pi.runtime.PiTableAction; 39 | import org.slf4j.Logger; 40 | import org.slf4j.LoggerFactory; 41 | 42 | import java.nio.ByteBuffer; 43 | import java.util.Collection; 44 | import java.util.List; 45 | import java.util.stream.Collectors; 46 | 47 | import static com.google.common.base.Preconditions.checkArgument; 48 | import static com.google.common.base.Preconditions.checkNotNull; 49 | import static org.onosproject.net.group.DefaultGroupBucket.createAllGroupBucket; 50 | import static org.onosproject.net.group.DefaultGroupBucket.createCloneGroupBucket; 51 | import static org.onosproject.srv6_usid.AppConstants.DEFAULT_FLOW_RULE_PRIORITY; 52 | 53 | public final class Utils { 54 | 55 | private static final Logger log = LoggerFactory.getLogger(Utils.class); 56 | 57 | public static GroupDescription buildMulticastGroup( 58 | ApplicationId appId, 59 | DeviceId deviceId, 60 | int groupId, 61 | Collection ports) { 62 | return buildReplicationGroup(appId, deviceId, groupId, ports, false); 63 | } 64 | 65 | public static GroupDescription buildCloneGroup( 66 | ApplicationId appId, 67 | DeviceId deviceId, 68 | int groupId, 69 | Collection ports) { 70 | return buildReplicationGroup(appId, deviceId, groupId, ports, true); 71 | } 72 | 73 | private static GroupDescription buildReplicationGroup( 74 | ApplicationId appId, 75 | DeviceId deviceId, 76 | int groupId, 77 | Collection ports, 78 | boolean isClone) { 79 | 80 | checkNotNull(deviceId); 81 | checkNotNull(appId); 82 | checkArgument(!ports.isEmpty()); 83 | 84 | final GroupKey groupKey = new DefaultGroupKey( 85 | ByteBuffer.allocate(4).putInt(groupId).array()); 86 | 87 | final List bucketList = ports.stream() 88 | .map(p -> DefaultTrafficTreatment.builder() 89 | .setOutput(p).build()) 90 | .map(t -> isClone ? createCloneGroupBucket(t) 91 | : createAllGroupBucket(t)) 92 | .collect(Collectors.toList()); 93 | 94 | return new DefaultGroupDescription( 95 | deviceId, 96 | isClone ? GroupDescription.Type.CLONE : GroupDescription.Type.ALL, 97 | new GroupBuckets(bucketList), 98 | groupKey, groupId, appId); 99 | } 100 | 101 | public static FlowRule buildFlowRule(DeviceId switchId, ApplicationId appId, 102 | String tableId, PiCriterion piCriterion, 103 | PiTableAction piAction) { 104 | return DefaultFlowRule.builder() 105 | .forDevice(switchId) 106 | .forTable(PiTableId.of(tableId)) 107 | .fromApp(appId) 108 | .withPriority(DEFAULT_FLOW_RULE_PRIORITY) 109 | .makePermanent() 110 | .withSelector(DefaultTrafficSelector.builder() 111 | .matchPi(piCriterion).build()) 112 | .withTreatment(DefaultTrafficTreatment.builder() 113 | .piTableAction(piAction).build()) 114 | .build(); 115 | } 116 | 117 | public static GroupDescription buildSelectGroup(DeviceId deviceId, 118 | String tableId, 119 | String actionProfileId, 120 | int groupId, 121 | Collection actions, 122 | ApplicationId appId) { 123 | 124 | final GroupKey groupKey = new PiGroupKey( 125 | PiTableId.of(tableId), PiActionProfileId.of(actionProfileId), groupId); 126 | final List buckets = actions.stream() 127 | .map(action -> DefaultTrafficTreatment.builder() 128 | .piTableAction(action).build()) 129 | .map(DefaultGroupBucket::createSelectGroupBucket) 130 | .collect(Collectors.toList()); 131 | return new DefaultGroupDescription( 132 | deviceId, 133 | GroupDescription.Type.SELECT, 134 | new GroupBuckets(buckets), 135 | groupKey, 136 | groupId, 137 | appId); 138 | } 139 | 140 | public static void sleep(int millis) { 141 | try { 142 | Thread.sleep(millis); 143 | } catch (InterruptedException e) { 144 | log.error("Interrupted!", e); 145 | Thread.currentThread().interrupt(); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/pipeconf/InterpreterImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.onosproject.srv6_usid.pipeconf; 18 | 19 | import com.google.common.collect.ImmutableList; 20 | import com.google.common.collect.ImmutableMap; 21 | import org.onlab.packet.DeserializationException; 22 | import org.onlab.packet.Ethernet; 23 | import org.onlab.util.ImmutableByteSequence; 24 | import org.onosproject.net.ConnectPoint; 25 | import org.onosproject.net.DeviceId; 26 | import org.onosproject.net.Port; 27 | import org.onosproject.net.PortNumber; 28 | import org.onosproject.net.device.DeviceService; 29 | import org.onosproject.net.driver.AbstractHandlerBehaviour; 30 | import org.onosproject.net.flow.TrafficTreatment; 31 | import org.onosproject.net.flow.criteria.Criterion; 32 | import org.onosproject.net.packet.DefaultInboundPacket; 33 | import org.onosproject.net.packet.InboundPacket; 34 | import org.onosproject.net.packet.OutboundPacket; 35 | import org.onosproject.net.pi.model.PiMatchFieldId; 36 | import org.onosproject.net.pi.model.PiPacketMetadataId; 37 | import org.onosproject.net.pi.model.PiPipelineInterpreter; 38 | import org.onosproject.net.pi.model.PiTableId; 39 | import org.onosproject.net.pi.runtime.PiAction; 40 | import org.onosproject.net.pi.runtime.PiPacketMetadata; 41 | import org.onosproject.net.pi.runtime.PiPacketOperation; 42 | 43 | import java.nio.ByteBuffer; 44 | import java.util.Collection; 45 | import java.util.List; 46 | import java.util.Map; 47 | import java.util.Optional; 48 | 49 | import static java.lang.String.format; 50 | import static java.util.stream.Collectors.toList; 51 | import static org.onlab.util.ImmutableByteSequence.copyFrom; 52 | import static org.onosproject.net.PortNumber.CONTROLLER; 53 | import static org.onosproject.net.PortNumber.FLOOD; 54 | import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT; 55 | import static org.onosproject.net.flow.instructions.Instructions.OutputInstruction; 56 | import static org.onosproject.net.pi.model.PiPacketOperationType.PACKET_OUT; 57 | import static org.onosproject.srv6_usid.AppConstants.CPU_PORT_ID; 58 | 59 | 60 | /** 61 | * Interpreter implementation. 62 | */ 63 | public class InterpreterImpl extends AbstractHandlerBehaviour 64 | implements PiPipelineInterpreter { 65 | 66 | 67 | // From v1model.p4 68 | private static final int V1MODEL_PORT_BITWIDTH = 9; 69 | 70 | // From P4Info. 71 | private static final Map CRITERION_MAP = 72 | new ImmutableMap.Builder() 73 | .put(Criterion.Type.IN_PORT, "standard_metadata.ingress_port") 74 | .put(Criterion.Type.ETH_DST, "hdr.ethernet.dst_addr") 75 | .put(Criterion.Type.ETH_SRC, "hdr.ethernet.src_addr") 76 | .put(Criterion.Type.ETH_TYPE, "hdr.ethernet.ether_type") 77 | .put(Criterion.Type.IPV6_DST, "hdr.ipv6.dst_addr") 78 | .put(Criterion.Type.IP_PROTO, "local_metadata.ip_proto") 79 | .put(Criterion.Type.ICMPV4_TYPE, "local_metadata.icmp_type") 80 | .put(Criterion.Type.ICMPV6_TYPE, "local_metadata.icmp_type") 81 | .build(); 82 | 83 | /** 84 | * Returns a collection of PI packet operations populated with metadata 85 | * specific for this pipeconf and equivalent to the given ONOS 86 | * OutboundPacket instance. 87 | * 88 | * @param packet ONOS OutboundPacket 89 | * @return collection of PI packet operations 90 | * @throws PiInterpreterException if the packet treatments cannot be 91 | * executed by this pipeline 92 | */ 93 | @Override 94 | public Collection mapOutboundPacket(OutboundPacket packet) 95 | throws PiInterpreterException { 96 | TrafficTreatment treatment = packet.treatment(); 97 | 98 | // Packet-out in main.p4 supports only setting the output port, 99 | // ---- END SOLUTION ---- 100 | // i.e. we only understand OUTPUT instructions. 101 | List outInstructions = treatment 102 | .allInstructions() 103 | .stream() 104 | .filter(i -> i.type().equals(OUTPUT)) 105 | .map(i -> (OutputInstruction) i) 106 | .collect(toList()); 107 | 108 | if (treatment.allInstructions().size() != outInstructions.size()) { 109 | // There are other instructions that are not of type OUTPUT. 110 | throw new PiInterpreterException("Treatment not supported: " + treatment); 111 | } 112 | 113 | ImmutableList.Builder builder = ImmutableList.builder(); 114 | for (OutputInstruction outInst : outInstructions) { 115 | if (outInst.port().isLogical() && !outInst.port().equals(FLOOD)) { 116 | throw new PiInterpreterException(format( 117 | "Packet-out on logical port '%s' not supported", 118 | outInst.port())); 119 | } else if (outInst.port().equals(FLOOD)) { 120 | // To emulate flooding, we create a packet-out operation for 121 | // each switch port. 122 | final DeviceService deviceService = handler().get(DeviceService.class); 123 | for (Port port : deviceService.getPorts(packet.sendThrough())) { 124 | builder.add(buildPacketOut(packet.data(), port.number().toLong())); 125 | } 126 | } else { 127 | // Create only one packet-out for the given OUTPUT instruction. 128 | builder.add(buildPacketOut(packet.data(), outInst.port().toLong())); 129 | } 130 | } 131 | return builder.build(); 132 | } 133 | 134 | /** 135 | * Builds a pipeconf-specific packet-out instance with the given payload and 136 | * egress port. 137 | * 138 | * @param pktData packet payload 139 | * @param portNumber egress port 140 | * @return packet-out 141 | * @throws PiInterpreterException if packet-out cannot be built 142 | */ 143 | private PiPacketOperation buildPacketOut(ByteBuffer pktData, long portNumber) 144 | throws PiInterpreterException { 145 | 146 | // Make sure port number can fit in v1model port metadata bitwidth. 147 | final ImmutableByteSequence portBytes; 148 | try { 149 | portBytes = copyFrom(portNumber).fit(V1MODEL_PORT_BITWIDTH); 150 | } catch (ImmutableByteSequence.ByteSequenceTrimException e) { 151 | throw new PiInterpreterException(format( 152 | "Port number %d too big, %s", portNumber, e.getMessage())); 153 | } 154 | 155 | // Create metadata instance for egress port. 156 | // TODO EXERCISE 1: modify metadata names to match P4 program 157 | // ---- START SOLUTION ---- 158 | final String outPortMetadataName = "egress_port"; 159 | // ---- END SOLUTION ---- 160 | final PiPacketMetadata outPortMetadata = PiPacketMetadata.builder() 161 | .withId(PiPacketMetadataId.of(outPortMetadataName)) 162 | .withValue(portBytes) 163 | .build(); 164 | 165 | // Build packet out. 166 | return PiPacketOperation.builder() 167 | .withType(PACKET_OUT) 168 | .withData(copyFrom(pktData)) 169 | .withMetadata(outPortMetadata) 170 | .build(); 171 | } 172 | 173 | /** 174 | * Returns an ONS InboundPacket equivalent to the given pipeconf-specific 175 | * packet-in operation. 176 | * 177 | * @param packetIn packet operation 178 | * @param deviceId ID of the device that originated the packet-in 179 | * @return inbound packet 180 | * @throws PiInterpreterException if the packet operation cannot be mapped 181 | * to an inbound packet 182 | */ 183 | @Override 184 | public InboundPacket mapInboundPacket(PiPacketOperation packetIn, DeviceId deviceId) 185 | throws PiInterpreterException { 186 | 187 | // Find the ingress_port metadata. 188 | // TODO EXERCISE 1: modify metadata names to match P4 program 189 | // ---- START SOLUTION ---- 190 | final String inportMetadataName = "ingress_port"; 191 | // ---- END SOLUTION ---- 192 | Optional inportMetadata = packetIn.metadatas() 193 | .stream() 194 | .filter(meta -> meta.id().id().equals(inportMetadataName)) 195 | .findFirst(); 196 | 197 | if (!inportMetadata.isPresent()) { 198 | throw new PiInterpreterException(format( 199 | "Missing metadata '%s' in packet-in received from '%s': %s", 200 | inportMetadataName, deviceId, packetIn)); 201 | } 202 | 203 | // Build ONOS InboundPacket instance with the given ingress port. 204 | 205 | // 1. Parse packet-in object into Ethernet packet instance. 206 | final byte[] payloadBytes = packetIn.data().asArray(); 207 | final ByteBuffer rawData = ByteBuffer.wrap(payloadBytes); 208 | final Ethernet ethPkt; 209 | try { 210 | ethPkt = Ethernet.deserializer().deserialize( 211 | payloadBytes, 0, packetIn.data().size()); 212 | } catch (DeserializationException dex) { 213 | throw new PiInterpreterException(dex.getMessage()); 214 | } 215 | 216 | // 2. Get ingress port 217 | final ImmutableByteSequence portBytes = inportMetadata.get().value(); 218 | final short portNum = portBytes.asReadOnlyBuffer().getShort(); 219 | final ConnectPoint receivedFrom = new ConnectPoint( 220 | deviceId, PortNumber.portNumber(portNum)); 221 | 222 | return new DefaultInboundPacket(receivedFrom, ethPkt, rawData); 223 | } 224 | 225 | @Override 226 | public Optional mapLogicalPortNumber(PortNumber port) { 227 | if (CONTROLLER.equals(port)) { 228 | return Optional.of(CPU_PORT_ID); 229 | } else { 230 | return Optional.empty(); 231 | } 232 | } 233 | 234 | @Override 235 | public Optional mapCriterionType(Criterion.Type type) { 236 | if (CRITERION_MAP.containsKey(type)) { 237 | return Optional.of(PiMatchFieldId.of(CRITERION_MAP.get(type))); 238 | } else { 239 | return Optional.empty(); 240 | } 241 | } 242 | 243 | @Override 244 | public PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId) 245 | throws PiInterpreterException { 246 | throw new PiInterpreterException("Treatment mapping not supported"); 247 | } 248 | 249 | @Override 250 | public Optional mapFlowRuleTableId(int flowRuleTableId) { 251 | return Optional.empty(); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/pipeconf/PipeconfLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.onosproject.srv6_usid.pipeconf; 18 | 19 | import org.onosproject.net.behaviour.Pipeliner; 20 | import org.onosproject.net.driver.DriverAdminService; 21 | import org.onosproject.net.driver.DriverProvider; 22 | import org.onosproject.net.pi.model.DefaultPiPipeconf; 23 | import org.onosproject.net.pi.model.PiPipeconf; 24 | import org.onosproject.net.pi.model.PiPipelineInterpreter; 25 | import org.onosproject.net.pi.model.PiPipelineModel; 26 | import org.onosproject.net.pi.service.PiPipeconfService; 27 | import org.onosproject.p4runtime.model.P4InfoParser; 28 | import org.onosproject.p4runtime.model.P4InfoParserException; 29 | import org.osgi.service.component.annotations.Activate; 30 | import org.osgi.service.component.annotations.Component; 31 | import org.osgi.service.component.annotations.Deactivate; 32 | import org.osgi.service.component.annotations.Reference; 33 | import org.osgi.service.component.annotations.ReferenceCardinality; 34 | import org.slf4j.Logger; 35 | import org.slf4j.LoggerFactory; 36 | 37 | import java.net.URL; 38 | import java.util.List; 39 | import java.util.stream.Collectors; 40 | 41 | import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON; 42 | import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT; 43 | import static org.onosproject.srv6_usid.AppConstants.PIPECONF_ID; 44 | 45 | /** 46 | * Component that builds and register the pipeconf at app activation. 47 | */ 48 | @Component(immediate = true, service = PipeconfLoader.class) 49 | public final class PipeconfLoader { 50 | 51 | private final Logger log = LoggerFactory.getLogger(getClass()); 52 | 53 | private static final String P4INFO_PATH = "/p4info.txt"; 54 | private static final String BMV2_JSON_PATH = "/bmv2.json"; 55 | 56 | 57 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 58 | private PiPipeconfService pipeconfService; 59 | 60 | @Reference(cardinality = ReferenceCardinality.MANDATORY) 61 | private DriverAdminService driverAdminService; 62 | 63 | @Activate 64 | public void activate() { 65 | // Registers the pipeconf at component activation. 66 | if (pipeconfService.getPipeconf(PIPECONF_ID).isPresent()) { 67 | // Remove first if already registered, to support reloading of the 68 | // pipeconf during the srv6_usid. 69 | pipeconfService.unregister(PIPECONF_ID); 70 | } 71 | removePipeconfDrivers(); 72 | try { 73 | pipeconfService.register(buildPipeconf()); 74 | } catch (P4InfoParserException e) { 75 | log.error("Unable to register " + PIPECONF_ID, e); 76 | } 77 | } 78 | 79 | @Deactivate 80 | public void deactivate() { 81 | // Do nothing. 82 | } 83 | 84 | private PiPipeconf buildPipeconf() throws P4InfoParserException { 85 | 86 | final URL p4InfoUrl = PipeconfLoader.class.getResource(P4INFO_PATH); 87 | final URL bmv2JsonUrlUrl = PipeconfLoader.class.getResource(BMV2_JSON_PATH); 88 | final PiPipelineModel pipelineModel = P4InfoParser.parse(p4InfoUrl); 89 | 90 | return DefaultPiPipeconf.builder() 91 | .withId(PIPECONF_ID) 92 | .withPipelineModel(pipelineModel) 93 | .addBehaviour(PiPipelineInterpreter.class, InterpreterImpl.class) 94 | .addBehaviour(Pipeliner.class, PipelinerImpl.class) 95 | .addExtension(P4_INFO_TEXT, p4InfoUrl) 96 | .addExtension(BMV2_JSON, bmv2JsonUrlUrl) 97 | .build(); 98 | } 99 | 100 | private void removePipeconfDrivers() { 101 | List driverProvidersToRemove = driverAdminService 102 | .getProviders().stream() 103 | .filter(p -> p.getDrivers().stream() 104 | .anyMatch(d -> d.name().endsWith(PIPECONF_ID.id()))) 105 | .collect(Collectors.toList()); 106 | 107 | if (driverProvidersToRemove.isEmpty()) { 108 | return; 109 | } 110 | 111 | log.info("Found {} outdated drivers for pipeconf '{}', removing...", 112 | driverProvidersToRemove.size(), PIPECONF_ID); 113 | 114 | driverProvidersToRemove.forEach(driverAdminService::unregisterProvider); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /app/src/main/java/org/onosproject/srv6_usid/pipeconf/PipelinerImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.onosproject.srv6_usid.pipeconf; 18 | 19 | import org.onosproject.net.DeviceId; 20 | import org.onosproject.net.PortNumber; 21 | import org.onosproject.net.behaviour.NextGroup; 22 | import org.onosproject.net.behaviour.Pipeliner; 23 | import org.onosproject.net.behaviour.PipelinerContext; 24 | import org.onosproject.net.driver.AbstractHandlerBehaviour; 25 | import org.onosproject.net.flow.DefaultFlowRule; 26 | import org.onosproject.net.flow.DefaultTrafficTreatment; 27 | import org.onosproject.net.flow.FlowRule; 28 | import org.onosproject.net.flow.FlowRuleService; 29 | import org.onosproject.net.flow.instructions.Instructions; 30 | import org.onosproject.net.flowobjective.FilteringObjective; 31 | import org.onosproject.net.flowobjective.ForwardingObjective; 32 | import org.onosproject.net.flowobjective.NextObjective; 33 | import org.onosproject.net.flowobjective.ObjectiveError; 34 | import org.onosproject.net.group.GroupDescription; 35 | import org.onosproject.net.group.GroupService; 36 | import org.onosproject.net.pi.model.PiActionId; 37 | import org.onosproject.net.pi.model.PiTableId; 38 | import org.onosproject.net.pi.runtime.PiAction; 39 | import org.onosproject.srv6_usid.common.Utils; 40 | import org.slf4j.Logger; 41 | 42 | import java.util.Collections; 43 | import java.util.List; 44 | 45 | import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT; 46 | import static org.onosproject.srv6_usid.AppConstants.CPU_CLONE_SESSION_ID; 47 | import static org.slf4j.LoggerFactory.getLogger; 48 | 49 | /** 50 | * Pipeliner implementation that maps all forwarding objectives to the ACL 51 | * table. All other types of objectives are not supported. 52 | */ 53 | public class PipelinerImpl extends AbstractHandlerBehaviour implements Pipeliner { 54 | 55 | // From the P4Info file 56 | private static final String ACL_TABLE = "IngressPipeImpl.acl"; 57 | private static final String CLONE_TO_CPU = "IngressPipeImpl.clone_to_cpu"; 58 | 59 | private final Logger log = getLogger(getClass()); 60 | 61 | private FlowRuleService flowRuleService; 62 | private GroupService groupService; 63 | private DeviceId deviceId; 64 | 65 | 66 | @Override 67 | public void init(DeviceId deviceId, PipelinerContext context) { 68 | this.deviceId = deviceId; 69 | this.flowRuleService = context.directory().get(FlowRuleService.class); 70 | this.groupService = context.directory().get(GroupService.class); 71 | } 72 | 73 | @Override 74 | public void filter(FilteringObjective obj) { 75 | obj.context().ifPresent(c -> c.onError(obj, ObjectiveError.UNSUPPORTED)); 76 | } 77 | 78 | @Override 79 | public void forward(ForwardingObjective obj) { 80 | if (obj.treatment() == null) { 81 | obj.context().ifPresent(c -> c.onError(obj, ObjectiveError.UNSUPPORTED)); 82 | } 83 | 84 | // Whether this objective specifies an OUTPUT:CONTROLLER instruction. 85 | final boolean hasCloneToCpuAction = obj.treatment() 86 | .allInstructions().stream() 87 | .filter(i -> i.type().equals(OUTPUT)) 88 | .map(i -> (Instructions.OutputInstruction) i) 89 | .anyMatch(i -> i.port().equals(PortNumber.CONTROLLER)); 90 | 91 | if (!hasCloneToCpuAction) { 92 | // We support only objectives for clone to CPU behaviours (e.g. for 93 | // host and link discovery) 94 | obj.context().ifPresent(c -> c.onError(obj, ObjectiveError.UNSUPPORTED)); 95 | } 96 | 97 | // Create an equivalent FlowRule with same selector and clone_to_cpu action. 98 | final PiAction cloneToCpuAction = PiAction.builder() 99 | .withId(PiActionId.of(CLONE_TO_CPU)) 100 | .build(); 101 | 102 | final FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() 103 | .forTable(PiTableId.of(ACL_TABLE)) 104 | .forDevice(deviceId) 105 | .withSelector(obj.selector()) 106 | .fromApp(obj.appId()) 107 | .withPriority(obj.priority()) 108 | .withTreatment(DefaultTrafficTreatment.builder() 109 | .piTableAction(cloneToCpuAction).build()); 110 | 111 | if (obj.permanent()) { 112 | ruleBuilder.makePermanent(); 113 | } else { 114 | ruleBuilder.makeTemporary(obj.timeout()); 115 | } 116 | 117 | final GroupDescription cloneGroup = Utils.buildCloneGroup( 118 | obj.appId(), 119 | deviceId, 120 | CPU_CLONE_SESSION_ID, 121 | // Ports where to clone the packet. 122 | // Just controller in this case. 123 | Collections.singleton(PortNumber.CONTROLLER)); 124 | 125 | switch (obj.op()) { 126 | case ADD: 127 | flowRuleService.applyFlowRules(ruleBuilder.build()); 128 | groupService.addGroup(cloneGroup); 129 | break; 130 | case REMOVE: 131 | flowRuleService.removeFlowRules(ruleBuilder.build()); 132 | groupService.removeGroup(deviceId, cloneGroup.appCookie(), obj.appId()); 133 | break; 134 | default: 135 | log.warn("Unknown operation {}", obj.op()); 136 | } 137 | 138 | obj.context().ifPresent(c -> c.onSuccess(obj)); 139 | } 140 | 141 | @Override 142 | public void next(NextObjective obj) { 143 | obj.context().ifPresent(c -> c.onError(obj, ObjectiveError.UNSUPPORTED)); 144 | } 145 | 146 | @Override 147 | public List getNextMappings(NextGroup nextGroup) { 148 | // We do not use nextObjectives or groups. 149 | return Collections.emptyList(); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /app/srv6-uSID.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /config/netcfg.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": { 3 | "device:r1": { 4 | "basic": { 5 | "managementAddress": "grpc://mininet:50001?device_id=1", 6 | "driver": "stratum-bmv2", 7 | "pipeconf": "org.p4.srv6_usid" 8 | }, 9 | "srv6DeviceConfig": { 10 | "myStationMac": "00:aa:00:00:00:01", 11 | "uN": "fcbb:bb00:01::", 12 | "uDX": "fcbb:bb00:01:fd00::", 13 | "isCore": false 14 | } 15 | }, 16 | "device:r2": { 17 | "basic": { 18 | "managementAddress": "grpc://mininet:50002?device_id=1", 19 | "driver": "stratum-bmv2", 20 | "pipeconf": "org.p4.srv6_usid" 21 | }, 22 | "srv6DeviceConfig": { 23 | "myStationMac": "00:aa:00:00:00:02", 24 | "uN": "fcbb:bb00:02::", 25 | "uDX": "fcbb:bb00:02:fd00::", 26 | "isCore": false 27 | } 28 | }, 29 | "device:r3": { 30 | "basic": { 31 | "managementAddress": "grpc://mininet:50003?device_id=1", 32 | "driver": "stratum-bmv2", 33 | "pipeconf": "org.p4.srv6_usid" 34 | }, 35 | "srv6DeviceConfig": { 36 | "myStationMac": "00:aa:00:00:00:03", 37 | "uN": "fcbb:bb00:03::", 38 | "isCore": true 39 | } 40 | }, 41 | "device:r4": { 42 | "basic": { 43 | "managementAddress": "grpc://mininet:50004?device_id=1", 44 | "driver": "stratum-bmv2", 45 | "pipeconf": "org.p4.srv6_usid" 46 | }, 47 | "srv6DeviceConfig": { 48 | "myStationMac": "00:aa:00:00:00:04", 49 | "uN": "fcbb:bb00:04::", 50 | "isCore": true 51 | } 52 | }, 53 | "device:r5": { 54 | "basic": { 55 | "managementAddress": "grpc://mininet:50005?device_id=1", 56 | "driver": "stratum-bmv2", 57 | "pipeconf": "org.p4.srv6_usid" 58 | }, 59 | "srv6DeviceConfig": { 60 | "myStationMac": "00:aa:00:00:00:05", 61 | "uN": "fcbb:bb00:05::", 62 | "isCore": true 63 | } 64 | }, 65 | "device:r6": { 66 | "basic": { 67 | "managementAddress": "grpc://mininet:50006?device_id=1", 68 | "driver": "stratum-bmv2", 69 | "pipeconf": "org.p4.srv6_usid" 70 | }, 71 | "srv6DeviceConfig": { 72 | "myStationMac": "00:aa:00:00:00:06", 73 | "uN": "fcbb:bb00:06::", 74 | "isCore": true 75 | } 76 | }, 77 | "device:r7": { 78 | "basic": { 79 | "managementAddress": "grpc://mininet:50007?device_id=1", 80 | "driver": "stratum-bmv2", 81 | "pipeconf": "org.p4.srv6_usid" 82 | }, 83 | "srv6DeviceConfig": { 84 | "myStationMac": "00:aa:00:00:00:07", 85 | "uN": "fcbb:bb00:07::", 86 | "isCore": true 87 | } 88 | }, 89 | "device:r8": { 90 | "basic": { 91 | "managementAddress": "grpc://mininet:50008?device_id=1", 92 | "driver": "stratum-bmv2", 93 | "pipeconf": "org.p4.srv6_usid" 94 | }, 95 | "srv6DeviceConfig": { 96 | "myStationMac": "00:aa:00:00:00:08", 97 | "uN": "fcbb:bb00:08::", 98 | "isCore": true 99 | } 100 | }, 101 | "device:r9": { 102 | "basic": { 103 | "managementAddress": "grpc://mininet:50009?device_id=1", 104 | "driver": "stratum-bmv2", 105 | "pipeconf": "org.p4.srv6_usid" 106 | }, 107 | "srv6DeviceConfig": { 108 | "myStationMac": "00:aa:00:00:00:09", 109 | "uN": "fcbb:bb00:09::", 110 | "uA": "fcbb:bb00:09:fa94::", 111 | "isCore": true 112 | } 113 | }, 114 | "device:r10": { 115 | "basic": { 116 | "managementAddress": "grpc://mininet:50010?device_id=1", 117 | "driver": "stratum-bmv2", 118 | "pipeconf": "org.p4.srv6_usid" 119 | }, 120 | "srv6DeviceConfig": { 121 | "myStationMac": "00:aa:00:00:00:0a", 122 | "uN": "fcbb:bb00:0a::", 123 | "uA": "fcbb:bb00:0a:faa5::", 124 | "isCore": true 125 | } 126 | }, 127 | "device:r11": { 128 | "basic": { 129 | "managementAddress": "grpc://mininet:50011?device_id=1", 130 | "driver": "stratum-bmv2", 131 | "pipeconf": "org.p4.srv6_usid" 132 | }, 133 | "srv6DeviceConfig": { 134 | "myStationMac": "00:aa:00:00:00:0b", 135 | "uN": "fcbb:bb00:0b::", 136 | "isCore": true 137 | } 138 | }, 139 | "device:r12": { 140 | "basic": { 141 | "managementAddress": "grpc://mininet:50012?device_id=1", 142 | "driver": "stratum-bmv2", 143 | "pipeconf": "org.p4.srv6_usid" 144 | }, 145 | "srv6DeviceConfig": { 146 | "myStationMac": "00:aa:00:00:00:0c", 147 | "uN": "fcbb:bb00:0c::", 148 | "isCore": true 149 | } 150 | }, 151 | "device:r13": { 152 | "basic": { 153 | "managementAddress": "grpc://mininet:50013?device_id=1", 154 | "driver": "stratum-bmv2", 155 | "pipeconf": "org.p4.srv6_usid" 156 | }, 157 | "srv6DeviceConfig": { 158 | "myStationMac": "00:aa:00:00:00:0d", 159 | "uN": "fcbb:bb00:0d::", 160 | "isCore": true 161 | } 162 | }, 163 | "device:r14": { 164 | "basic": { 165 | "managementAddress": "grpc://mininet:50014?device_id=1", 166 | "driver": "stratum-bmv2", 167 | "pipeconf": "org.p4.srv6_usid" 168 | }, 169 | "srv6DeviceConfig": { 170 | "myStationMac": "00:aa:00:00:00:0e", 171 | "uN": "fcbb:bb00:0e::", 172 | "isCore": true 173 | } 174 | } 175 | }, 176 | "ports": { 177 | "device:r1/3": { 178 | "interfaces": [ 179 | { 180 | "name": "r1/3", 181 | "ips": [ 182 | "2001:1:1::ff/64" 183 | ] 184 | } 185 | ] 186 | }, 187 | "device:r2/3": { 188 | "interfaces": [ 189 | { 190 | "name": "r2-3", 191 | "ips": [ 192 | "2001:1:2::ff/64" 193 | ] 194 | } 195 | ] 196 | } 197 | }, 198 | "hosts": { 199 | "00:00:00:00:00:10/None": { 200 | "basic": { 201 | "name": "h1" 202 | } 203 | }, 204 | "00:00:00:00:00:20/None": { 205 | "basic": { 206 | "name": "h2" 207 | } 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /config/routing_tables.txt: -------------------------------------------------------------------------------- 1 | ############ r1 2 | #h1 3 | route-insert device:r1 2001:1:1::1 128 00:00:00:00:00:10 4 | #h2 5 | route-insert device:r1 2001:1:2::1 128 00:aa:00:00:00:09 6 | 7 | #r2 8 | route-insert device:r1 fcbb:bb00:02:: 48 00:aa:00:00:00:09 9 | #r3 10 | route-insert device:r1 fcbb:bb00:03:: 48 00:aa:00:00:00:09 11 | #r4 12 | route-insert device:r1 fcbb:bb00:04:: 48 00:aa:00:00:00:04 13 | #r5 14 | route-insert device:r1 fcbb:bb00:05:: 48 00:aa:00:00:00:04 15 | #r6 16 | route-insert device:r1 fcbb:bb00:06:: 48 00:aa:00:00:00:09 17 | #r7 18 | route-insert device:r1 fcbb:bb00:07:: 48 00:aa:00:00:00:09 19 | #r8 20 | route-insert device:r1 fcbb:bb00:08:: 48 00:aa:00:00:00:04 21 | #r9 22 | route-insert device:r1 fcbb:bb00:09:: 48 00:aa:00:00:00:09 23 | #r10 24 | route-insert device:r1 fcbb:bb00:0a:: 48 00:aa:00:00:00:04 25 | #r11 26 | route-insert device:r1 fcbb:bb00:0b:: 48 00:aa:00:00:00:04 27 | #r12 28 | route-insert device:r1 fcbb:bb00:0c:: 48 00:aa:00:00:00:04 29 | #r13 30 | route-insert device:r1 fcbb:bb00:0d:: 48 00:aa:00:00:00:09 31 | #r14 32 | route-insert device:r1 fcbb:bb00:0e:: 48 00:aa:00:00:00:09 33 | 34 | ############ r2 35 | #h1 36 | route-insert device:r2 2001:1:1::1 128 00:aa:00:00:00:0e 37 | #h2 38 | route-insert device:r2 2001:1:2::1 128 00:00:00:00:00:20 39 | 40 | #r1 41 | route-insert device:r2 fcbb:bb00:01:: 48 00:aa:00:00:00:0e 42 | #r3 43 | route-insert device:r2 fcbb:bb00:03:: 48 00:aa:00:00:00:03 44 | #r4 45 | route-insert device:r2 fcbb:bb00:04:: 48 00:aa:00:00:00:0e 46 | #r5 47 | route-insert device:r2 fcbb:bb00:05:: 48 00:aa:00:00:00:0e 48 | #r6 49 | route-insert device:r2 fcbb:bb00:06:: 48 00:aa:00:00:00:03 50 | #r7 51 | route-insert device:r2 fcbb:bb00:07:: 48 00:aa:00:00:00:03 52 | #r8 53 | route-insert device:r2 fcbb:bb00:08:: 48 00:aa:00:00:00:0e 54 | #r9 55 | route-insert device:r2 fcbb:bb00:09:: 48 00:aa:00:00:00:0e 56 | #r10 57 | route-insert device:r2 fcbb:bb00:0a:: 48 00:aa:00:00:00:03 58 | #r11 59 | route-insert device:r2 fcbb:bb00:0b:: 48 00:aa:00:00:00:03 60 | #r12 61 | route-insert device:r2 fcbb:bb00:0c:: 48 00:aa:00:00:00:03 62 | #r13 63 | route-insert device:r2 fcbb:bb00:0d:: 48 00:aa:00:00:00:03 64 | #r14 65 | route-insert device:r2 fcbb:bb00:0e:: 48 00:aa:00:00:00:0e 66 | 67 | 68 | ############ r3 69 | #h1 70 | route-insert device:r3 2001:1:1::1 128 00:aa:00:00:00:02 71 | #h2 72 | route-insert device:r3 2001:1:2::1 128 00:aa:00:00:00:02 73 | 74 | #r1 75 | route-insert device:r3 fcbb:bb00:01:: 48 00:aa:00:00:00:0e 76 | #r2 77 | route-insert device:r3 fcbb:bb00:02:: 48 00:aa:00:00:00:02 78 | #r4 79 | route-insert device:r3 fcbb:bb00:04:: 48 00:aa:00:00:00:0d 80 | #r5 81 | route-insert device:r3 fcbb:bb00:05:: 48 00:aa:00:00:00:0d 82 | #r6 83 | route-insert device:r3 fcbb:bb00:06:: 48 00:aa:00:00:00:06 84 | #r7 85 | route-insert device:r3 fcbb:bb00:07:: 48 00:aa:00:00:00:06 86 | #r8 87 | route-insert device:r3 fcbb:bb00:08:: 48 00:aa:00:00:00:06 88 | #r9 89 | route-insert device:r3 fcbb:bb00:09:: 48 00:aa:00:00:00:0e 90 | #r10 91 | route-insert device:r3 fcbb:bb00:0a:: 48 00:aa:00:00:00:0d 92 | #r11 93 | route-insert device:r3 fcbb:bb00:0b:: 48 00:aa:00:00:00:0d 94 | #r12 95 | route-insert device:r3 fcbb:bb00:0c:: 48 00:aa:00:00:00:0d 96 | #r13 97 | route-insert device:r3 fcbb:bb00:0d:: 48 00:aa:00:00:00:0d 98 | #r14 99 | route-insert device:r3 fcbb:bb00:0e:: 48 00:aa:00:00:00:0e 100 | 101 | 102 | ############ r4 103 | #h1 104 | route-insert device:r4 2001:1:1::1 128 00:aa:00:00:00:01 105 | #h2 106 | route-insert device:r4 2001:1:2::1 128 00:aa:00:00:00:09 107 | 108 | #r1 109 | route-insert device:r4 fcbb:bb00:01:: 48 00:aa:00:00:00:01 110 | #r2 111 | route-insert device:r4 fcbb:bb00:02:: 48 00:aa:00:00:00:09 112 | #r3 113 | route-insert device:r4 fcbb:bb00:03:: 48 00:aa:00:00:00:0a 114 | #r5 115 | route-insert device:r4 fcbb:bb00:05:: 48 00:aa:00:00:00:05 116 | #r6 117 | route-insert device:r4 fcbb:bb00:06:: 48 00:aa:00:00:00:0a 118 | #r7 119 | route-insert device:r4 fcbb:bb00:07:: 48 00:aa:00:00:00:05 120 | #r8 121 | route-insert device:r4 fcbb:bb00:08:: 48 00:aa:00:00:00:05 122 | #r9 123 | route-insert device:r4 fcbb:bb00:09:: 48 00:aa:00:00:00:09 124 | #r10 125 | route-insert device:r4 fcbb:bb00:0a:: 48 00:aa:00:00:00:0a 126 | #r11 127 | route-insert device:r4 fcbb:bb00:0b:: 48 00:aa:00:00:00:0a 128 | #r12 129 | route-insert device:r4 fcbb:bb00:0c:: 48 00:aa:00:00:00:0a 130 | #r13 131 | route-insert device:r4 fcbb:bb00:0d:: 48 00:aa:00:00:00:0a 132 | #r14 133 | route-insert device:r4 fcbb:bb00:0e:: 48 00:aa:00:00:00:09 134 | 135 | 136 | ############ r5 137 | #h1 138 | route-insert device:r5 2001:1:1::1 128 00:aa:00:00:00:04 139 | #h2 140 | route-insert device:r5 2001:1:2::1 128 00:aa:00:00:00:0a 141 | 142 | #r1 143 | route-insert device:r5 fcbb:bb00:01:: 48 00:aa:00:00:00:04 144 | #r2 145 | route-insert device:r5 fcbb:bb00:02:: 48 00:aa:00:00:00:0a 146 | #r3 147 | route-insert device:r5 fcbb:bb00:03:: 48 00:aa:00:00:00:0a 148 | #r4 149 | route-insert device:r5 fcbb:bb00:04:: 48 00:aa:00:00:00:04 150 | #r6 151 | route-insert device:r5 fcbb:bb00:06:: 48 00:aa:00:00:00:0b 152 | #r7 153 | route-insert device:r5 fcbb:bb00:07:: 48 00:aa:00:00:00:08 154 | #r8 155 | route-insert device:r5 fcbb:bb00:08:: 48 00:aa:00:00:00:08 156 | #r9 157 | route-insert device:r5 fcbb:bb00:09:: 48 00:aa:00:00:00:0a 158 | #r10 159 | route-insert device:r5 fcbb:bb00:0a:: 48 00:aa:00:00:00:0a 160 | #r11 161 | route-insert device:r5 fcbb:bb00:0b:: 48 00:aa:00:00:00:0b 162 | #r12 163 | route-insert device:r5 fcbb:bb00:0c:: 48 00:aa:00:00:00:0b 164 | #r13 165 | route-insert device:r5 fcbb:bb00:0d:: 48 00:aa:00:00:00:0a 166 | #r14 167 | route-insert device:r5 fcbb:bb00:0e:: 48 00:aa:00:00:00:0a 168 | 169 | ############ r6 170 | #h1 171 | route-insert device:r6 2001:1:1::1 128 00:aa:00:00:00:0d 172 | #h2 173 | route-insert device:r6 2001:1:2::1 128 00:aa:00:00:00:03 174 | 175 | #r1 176 | route-insert device:r6 fcbb:bb00:01:: 48 00:aa:00:00:00:0d 177 | #r2 178 | route-insert device:r6 fcbb:bb00:02:: 48 00:aa:00:00:00:03 179 | #r3 180 | route-insert device:r6 fcbb:bb00:03:: 48 00:aa:00:00:00:03 181 | #r4 182 | route-insert device:r6 fcbb:bb00:04:: 48 00:aa:00:00:00:0d 183 | #r5 184 | route-insert device:r6 fcbb:bb00:05:: 48 00:aa:00:00:00:0d 185 | #r7 186 | route-insert device:r6 fcbb:bb00:07:: 48 00:aa:00:00:00:07 187 | #r8 188 | route-insert device:r6 fcbb:bb00:08:: 48 00:aa:00:00:00:07 189 | #r9 190 | route-insert device:r6 fcbb:bb00:09:: 48 00:aa:00:00:00:0d 191 | #r10 192 | route-insert device:r6 fcbb:bb00:0a:: 48 00:aa:00:00:00:0d 193 | #r11 194 | route-insert device:r6 fcbb:bb00:0b:: 48 00:aa:00:00:00:0c 195 | #r12 196 | route-insert device:r6 fcbb:bb00:0c:: 48 00:aa:00:00:00:0c 197 | #r13 198 | route-insert device:r6 fcbb:bb00:0d:: 48 00:aa:00:00:00:0d 199 | #r14 200 | route-insert device:r6 fcbb:bb00:0e:: 48 00:aa:00:00:00:0d 201 | 202 | ############ r7 203 | #h1 204 | route-insert device:r7 2001:1:1::1 128 00:aa:00:00:00:08 205 | #h2 206 | route-insert device:r7 2001:1:2::1 128 00:aa:00:00:00:06 207 | 208 | #r1 209 | route-insert device:r7 fcbb:bb00:01:: 48 00:aa:00:00:00:0c 210 | #r2 211 | route-insert device:r7 fcbb:bb00:02:: 48 00:aa:00:00:00:06 212 | #r3 213 | route-insert device:r7 fcbb:bb00:03:: 48 00:aa:00:00:00:06 214 | #r4 215 | route-insert device:r7 fcbb:bb00:04:: 48 00:aa:00:00:00:0c 216 | #r5 217 | route-insert device:r7 fcbb:bb00:05:: 48 00:aa:00:00:00:08 218 | #r6 219 | route-insert device:r7 fcbb:bb00:06:: 48 00:aa:00:00:00:06 220 | #r8 221 | route-insert device:r7 fcbb:bb00:08:: 48 00:aa:00:00:00:08 222 | #r9 223 | route-insert device:r7 fcbb:bb00:09:: 48 00:aa:00:00:00:0c 224 | #r10 225 | route-insert device:r7 fcbb:bb00:0a:: 48 00:aa:00:00:00:0c 226 | #r11 227 | route-insert device:r7 fcbb:bb00:0b:: 48 00:aa:00:00:00:0c 228 | #r12 229 | route-insert device:r7 fcbb:bb00:0c:: 48 00:aa:00:00:00:0c 230 | #r13 231 | route-insert device:r7 fcbb:bb00:0d:: 48 00:aa:00:00:00:0c 232 | #r14 233 | route-insert device:r7 fcbb:bb00:0e:: 48 00:aa:00:00:00:0c 234 | 235 | ############ r8 236 | #h1 237 | route-insert device:r8 2001:1:1::1 128 00:aa:00:00:00:05 238 | #h2 239 | route-insert device:r8 2001:1:2::1 128 00:aa:00:00:00:07 240 | 241 | #r1 242 | route-insert device:r8 fcbb:bb00:01:: 48 00:aa:00:00:00:05 243 | #r2 244 | route-insert device:r8 fcbb:bb00:02:: 48 00:aa:00:00:00:0b 245 | #r3 246 | route-insert device:r8 fcbb:bb00:03:: 48 00:aa:00:00:00:0b 247 | #r4 248 | route-insert device:r8 fcbb:bb00:04:: 48 00:aa:00:00:00:05 249 | #r5 250 | route-insert device:r8 fcbb:bb00:05:: 48 00:aa:00:00:00:05 251 | #r6 252 | route-insert device:r8 fcbb:bb00:06:: 48 00:aa:00:00:00:07 253 | #r7 254 | route-insert device:r8 fcbb:bb00:07:: 48 00:aa:00:00:00:07 255 | #r9 256 | route-insert device:r8 fcbb:bb00:09:: 48 00:aa:00:00:00:0b 257 | #r10 258 | route-insert device:r8 fcbb:bb00:0a:: 48 00:aa:00:00:00:0b 259 | #r11 260 | route-insert device:r8 fcbb:bb00:0b:: 48 00:aa:00:00:00:0b 261 | #r12 262 | route-insert device:r8 fcbb:bb00:0c:: 48 00:aa:00:00:00:0b 263 | #r13 264 | route-insert device:r8 fcbb:bb00:0d:: 48 00:aa:00:00:00:0b 265 | #r14 266 | route-insert device:r8 fcbb:bb00:0e:: 48 00:aa:00:00:00:0b 267 | 268 | ############ r9 269 | #h1 270 | route-insert device:r9 2001:1:1::1 128 00:aa:00:00:00:01 271 | #h2 272 | route-insert device:r9 2001:1:2::1 128 00:aa:00:00:00:0e 273 | 274 | #r1 275 | route-insert device:r9 fcbb:bb00:01:: 48 00:aa:00:00:00:01 276 | #r2 277 | route-insert device:r9 fcbb:bb00:02:: 48 00:aa:00:00:00:0e 278 | #r3 279 | route-insert device:r9 fcbb:bb00:03:: 48 00:aa:00:00:00:0e 280 | #r4 281 | route-insert device:r9 fcbb:bb00:04:: 48 00:aa:00:00:00:04 282 | #r5 283 | route-insert device:r9 fcbb:bb00:05:: 48 00:aa:00:00:00:0a 284 | #r6 285 | route-insert device:r9 fcbb:bb00:06:: 48 00:aa:00:00:00:0d 286 | #r7 287 | route-insert device:r9 fcbb:bb00:07:: 48 00:aa:00:00:00:0d 288 | #r8 289 | route-insert device:r9 fcbb:bb00:08:: 48 00:aa:00:00:00:0a 290 | #r10 291 | route-insert device:r9 fcbb:bb00:0a:: 48 00:aa:00:00:00:0a 292 | #r11 293 | route-insert device:r9 fcbb:bb00:0b:: 48 00:aa:00:00:00:0a 294 | #r12 295 | route-insert device:r9 fcbb:bb00:0c:: 48 00:aa:00:00:00:0a 296 | #r13 297 | route-insert device:r9 fcbb:bb00:0d:: 48 00:aa:00:00:00:0d 298 | #r14 299 | route-insert device:r9 fcbb:bb00:0e:: 48 00:aa:00:00:00:0e 300 | 301 | ############ r10 302 | #h1 303 | route-insert device:r10 2001:1:1::1 128 00:aa:00:00:00:04 304 | #h2 305 | route-insert device:r10 2001:1:2::1 128 00:aa:00:00:00:0d 306 | 307 | #r1 308 | route-insert device:r10 fcbb:bb00:01:: 48 00:aa:00:00:00:09 309 | #r2 310 | route-insert device:r10 fcbb:bb00:02:: 48 00:aa:00:00:00:0e 311 | #r3 312 | route-insert device:r10 fcbb:bb00:03:: 48 00:aa:00:00:00:0d 313 | #r4 314 | route-insert device:r10 fcbb:bb00:04:: 48 00:aa:00:00:00:04 315 | #r5 316 | route-insert device:r10 fcbb:bb00:05:: 48 00:aa:00:00:00:05 317 | #r6 318 | route-insert device:r10 fcbb:bb00:06:: 48 00:aa:00:00:00:0d 319 | #r7 320 | route-insert device:r10 fcbb:bb00:07:: 48 00:aa:00:00:00:0c 321 | #r8 322 | route-insert device:r10 fcbb:bb00:08:: 48 00:aa:00:00:00:0b 323 | #r9 324 | route-insert device:r10 fcbb:bb00:09:: 48 00:aa:00:00:00:09 325 | #r11 326 | route-insert device:r10 fcbb:bb00:0b:: 48 00:aa:00:00:00:0b 327 | #r12 328 | route-insert device:r10 fcbb:bb00:0c:: 48 00:aa:00:00:00:0c 329 | #r13 330 | route-insert device:r10 fcbb:bb00:0d:: 48 00:aa:00:00:00:0d 331 | #r14 332 | route-insert device:r10 fcbb:bb00:0e:: 48 00:aa:00:00:00:0e 333 | 334 | ############ r11 335 | #h1 336 | route-insert device:r11 2001:1:1::1 128 00:aa:00:00:00:0a 337 | #h2 338 | route-insert device:r11 2001:1:2::1 128 00:aa:00:00:00:0d 339 | 340 | #r1 341 | route-insert device:r11 fcbb:bb00:01:: 48 00:aa:00:00:00:0a 342 | #r2 343 | route-insert device:r11 fcbb:bb00:02:: 48 00:aa:00:00:00:0d 344 | #r3 345 | route-insert device:r11 fcbb:bb00:03:: 48 00:aa:00:00:00:0d 346 | #r4 347 | route-insert device:r11 fcbb:bb00:04:: 48 00:aa:00:00:00:0a 348 | #r5 349 | route-insert device:r11 fcbb:bb00:05:: 48 00:aa:00:00:00:05 350 | #r6 351 | route-insert device:r11 fcbb:bb00:06:: 48 00:aa:00:00:00:0c 352 | #r7 353 | route-insert device:r11 fcbb:bb00:07:: 48 00:aa:00:00:00:0c 354 | #r8 355 | route-insert device:r11 fcbb:bb00:08:: 48 00:aa:00:00:00:08 356 | #r9 357 | route-insert device:r11 fcbb:bb00:09:: 48 00:aa:00:00:00:0a 358 | #r10 359 | route-insert device:r11 fcbb:bb00:0a:: 48 00:aa:00:00:00:0a 360 | #r12 361 | route-insert device:r11 fcbb:bb00:0c:: 48 00:aa:00:00:00:0c 362 | #r13 363 | route-insert device:r11 fcbb:bb00:0d:: 48 00:aa:00:00:00:0d 364 | #r14 365 | route-insert device:r11 fcbb:bb00:0e:: 48 00:aa:00:00:00:0d 366 | 367 | 368 | ############ r12 369 | #h1 370 | route-insert device:r12 2001:1:1::1 128 00:aa:00:00:00:0a 371 | #h2 372 | route-insert device:r12 2001:1:2::1 128 00:aa:00:00:00:0d 373 | 374 | #r1 375 | route-insert device:r12 fcbb:bb00:01:: 48 00:aa:00:00:00:0a 376 | #r2 377 | route-insert device:r12 fcbb:bb00:02:: 48 00:aa:00:00:00:0d 378 | #r3 379 | route-insert device:r12 fcbb:bb00:03:: 48 00:aa:00:00:00:0d 380 | #r4 381 | route-insert device:r12 fcbb:bb00:04:: 48 00:aa:00:00:00:0a 382 | #r5 383 | route-insert device:r12 fcbb:bb00:05:: 48 00:aa:00:00:00:0b 384 | #r6 385 | route-insert device:r12 fcbb:bb00:06:: 48 00:aa:00:00:00:06 386 | #r7 387 | route-insert device:r12 fcbb:bb00:07:: 48 00:aa:00:00:00:07 388 | #r8 389 | route-insert device:r12 fcbb:bb00:08:: 48 00:aa:00:00:00:0b 390 | #r9 391 | route-insert device:r12 fcbb:bb00:09:: 48 00:aa:00:00:00:0a 392 | #r10 393 | route-insert device:r12 fcbb:bb00:0a:: 48 00:aa:00:00:00:0a 394 | #r11 395 | route-insert device:r12 fcbb:bb00:0b:: 48 00:aa:00:00:00:0b 396 | #r13 397 | route-insert device:r12 fcbb:bb00:0d:: 48 00:aa:00:00:00:0d 398 | #r14 399 | route-insert device:r12 fcbb:bb00:0e:: 48 00:aa:00:00:00:0d 400 | 401 | ############ r13 402 | #h1 403 | route-insert device:r13 2001:1:1::1 128 00:aa:00:00:00:09 404 | #h2 405 | route-insert device:r13 2001:1:2::1 128 00:aa:00:00:00:0e 406 | 407 | #r1 408 | route-insert device:r13 fcbb:bb00:01:: 48 00:aa:00:00:00:09 409 | #r2 410 | route-insert device:r13 fcbb:bb00:02:: 48 00:aa:00:00:00:0e 411 | #r3 412 | route-insert device:r13 fcbb:bb00:03:: 48 00:aa:00:00:00:03 413 | #r4 414 | route-insert device:r13 fcbb:bb00:04:: 48 00:aa:00:00:00:0a 415 | #r5 416 | route-insert device:r13 fcbb:bb00:05:: 48 00:aa:00:00:00:0a 417 | #r6 418 | route-insert device:r13 fcbb:bb00:06:: 48 00:aa:00:00:00:06 419 | #r7 420 | route-insert device:r13 fcbb:bb00:07:: 48 00:aa:00:00:00:0c 421 | #r8 422 | route-insert device:r13 fcbb:bb00:08:: 48 00:aa:00:00:00:0b 423 | #r9 424 | route-insert device:r13 fcbb:bb00:09:: 48 00:aa:00:00:00:09 425 | #r10 426 | route-insert device:r13 fcbb:bb00:0a:: 48 00:aa:00:00:00:0a 427 | #r11 428 | route-insert device:r13 fcbb:bb00:0b:: 48 00:aa:00:00:00:0b 429 | #r12 430 | route-insert device:r13 fcbb:bb00:0c:: 48 00:aa:00:00:00:0c 431 | #r14 432 | route-insert device:r13 fcbb:bb00:0e:: 48 00:aa:00:00:00:0e 433 | 434 | ############ r14 435 | #h1 436 | route-insert device:r14 2001:1:1::1 128 00:aa:00:00:00:09 437 | #h2 438 | route-insert device:r14 2001:1:2::1 128 00:aa:00:00:00:02 439 | 440 | #r1 441 | route-insert device:r14 fcbb:bb00:01:: 48 00:aa:00:00:00:09 442 | #r2 443 | route-insert device:r14 fcbb:bb00:02:: 48 00:aa:00:00:00:02 444 | #r3 445 | route-insert device:r14 fcbb:bb00:03:: 48 00:aa:00:00:00:03 446 | #r4 447 | route-insert device:r14 fcbb:bb00:04:: 48 00:aa:00:00:00:09 448 | #r5 449 | route-insert device:r14 fcbb:bb00:05:: 48 00:aa:00:00:00:0a 450 | #r6 451 | route-insert device:r14 fcbb:bb00:06:: 48 00:aa:00:00:00:0d 452 | #r7 453 | route-insert device:r14 fcbb:bb00:07:: 48 00:aa:00:00:00:0d 454 | #r8 455 | route-insert device:r14 fcbb:bb00:08:: 48 00:aa:00:00:00:0a 456 | #r9 457 | route-insert device:r14 fcbb:bb00:09:: 48 00:aa:00:00:00:09 458 | #r10 459 | route-insert device:r14 fcbb:bb00:0a:: 48 00:aa:00:00:00:0a 460 | #r11 461 | route-insert device:r14 fcbb:bb00:0b:: 48 00:aa:00:00:00:0a 462 | #r12 463 | route-insert device:r14 fcbb:bb00:0c:: 48 00:aa:00:00:00:0d 464 | #r13 465 | route-insert device:r14 fcbb:bb00:0d:: 48 00:aa:00:00:00:0d 466 | -------------------------------------------------------------------------------- /config/srv6_insert.txt: -------------------------------------------------------------------------------- 1 | # use case 1 2 | srv6-insert device:r1 fcbb:bb00:8:7:2:fd00:: 2001:1:2::1 3 | srv6-insert device:r2 fcbb:bb00:7:8:1:fd00:: 2001:1:1::1 4 | 5 | # use case 2 6 | srv6-insert device:r1 fcbb:bb00:9:fa94:a:faa5:b:c fcbb:bb00:e:2:fd00:: 2001:1:2::1 7 | -------------------------------------------------------------------------------- /config/ua_config.txt: -------------------------------------------------------------------------------- 1 | uA-insert device:r9 fcbb:bb00:9:fa94:: fcbb:bb00:4:: 00:aa:00:00:00:04 2 | uA-insert device:r10 fcbb:bb00:a:faa5:: fcbb:bb00:5:: 00:aa:00:00:00:05 3 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | mininet: 5 | image: opennetworking/ngsdn-tutorial:stratum_bmv2 6 | hostname: mininet 7 | container_name: mininet 8 | privileged: true 9 | tty: true 10 | stdin_open: true 11 | restart: always 12 | volumes: 13 | - ./tmp:/tmp 14 | - ./mininet:/mininet 15 | ports: 16 | - "50001:50001" 17 | - "50002:50002" 18 | - "50003:50003" 19 | - "50004:50004" 20 | - "50005:50005" 21 | - "50006:50006" 22 | - "50007:50007" 23 | - "50008:50008" 24 | - "50009:50009" 25 | - "50010:50010" 26 | - "50011:50011" 27 | - "50012:50012" 28 | - "50013:50013" 29 | - "50015:50014" 30 | entrypoint: "/mininet/topo.py" 31 | onos: 32 | image: onosproject/onos:2.2.2 33 | hostname: onos 34 | container_name: onos 35 | ports: 36 | - "8181:8181" # HTTP 37 | - "8101:8101" # SSH (CLI) 38 | volumes: 39 | - ./tmp/onos:/root/onos/apache-karaf-4.2.8/data/tmp 40 | - ./config:/config 41 | environment: 42 | - ONOS_APPS=gui2,drivers.bmv2,lldpprovider,hostprovider 43 | links: 44 | - mininet 45 | -------------------------------------------------------------------------------- /ecmp_encap_split.txt: -------------------------------------------------------------------------------- 1 | ############### SPLIT TRAFFIC across multiple SID lists with ECMP 2 | # 3 | # Create a group for the ecmp selector 4 | act_prof_create_group IngressPipeImpl.ecmp_selector 5 | # 6 | # Create members for the ecmp selector group (last two arguments are action parameters). 7 | # Here we assume we want to split traffic in two SID lists with a weight of 70% for the 8 | # first and a weight of 30% for the second path. To implement this we create 10 different 9 | # members of the group: 7 (equal between each other) represent the first path, the other 10 | # three represent the second path. In this way, the traffic is statistically splitted 11 | # across the two SID lists. 12 | # 13 | # First path -> 7 entries (70%) 14 | # 15 | act_prof_create_member IngressPipeImpl.ecmp_selector IngressPipeImpl.usid_encap_1_v4 0xfcbbbbbb000000000000000000000001 0xfcbbbbbb000100020003000400050006 16 | act_prof_create_member IngressPipeImpl.ecmp_selector IngressPipeImpl.usid_encap_1_v4 0xfcbbbbbb000000000000000000000001 0xfcbbbbbb000100020003000400050006 17 | act_prof_create_member IngressPipeImpl.ecmp_selector IngressPipeImpl.usid_encap_1_v4 0xfcbbbbbb000000000000000000000001 0xfcbbbbbb000100020003000400050006 18 | act_prof_create_member IngressPipeImpl.ecmp_selector IngressPipeImpl.usid_encap_1_v4 0xfcbbbbbb000000000000000000000001 0xfcbbbbbb000100020003000400050006 19 | act_prof_create_member IngressPipeImpl.ecmp_selector IngressPipeImpl.usid_encap_1_v4 0xfcbbbbbb000000000000000000000001 0xfcbbbbbb000100020003000400050006 20 | act_prof_create_member IngressPipeImpl.ecmp_selector IngressPipeImpl.usid_encap_1_v4 0xfcbbbbbb000000000000000000000001 0xfcbbbbbb000100020003000400050006 21 | act_prof_create_member IngressPipeImpl.ecmp_selector IngressPipeImpl.usid_encap_1_v4 0xfcbbbbbb000000000000000000000001 0xfcbbbbbb000100020003000400050006 22 | # 23 | # Second path -> 3 entries (30%) 24 | # 25 | act_prof_create_member IngressPipeImpl.ecmp_selector IngressPipeImpl.usid_encap_1_v4 0xfcbbbbbb000000000000000000000001 0xfcbbbbbb000a000b000c000d000e000f 26 | act_prof_create_member IngressPipeImpl.ecmp_selector IngressPipeImpl.usid_encap_1_v4 0xfcbbbbbb000000000000000000000001 0xfcbbbbbb000a000b000c000d000e000f 27 | act_prof_create_member IngressPipeImpl.ecmp_selector IngressPipeImpl.usid_encap_1_v4 0xfcbbbbbb000000000000000000000001 0xfcbbbbbb000a000b000c000d000e000f 28 | # 29 | # add the created members to the group 30 | # act_prof_add_member_to_group 31 | act_prof_add_member_to_group IngressPipeImpl.ecmp_selector 0 0 32 | act_prof_add_member_to_group IngressPipeImpl.ecmp_selector 1 0 33 | act_prof_add_member_to_group IngressPipeImpl.ecmp_selector 2 0 34 | act_prof_add_member_to_group IngressPipeImpl.ecmp_selector 3 0 35 | act_prof_add_member_to_group IngressPipeImpl.ecmp_selector 4 0 36 | act_prof_add_member_to_group IngressPipeImpl.ecmp_selector 5 0 37 | act_prof_add_member_to_group IngressPipeImpl.ecmp_selector 6 0 38 | act_prof_add_member_to_group IngressPipeImpl.ecmp_selector 7 0 39 | act_prof_add_member_to_group IngressPipeImpl.ecmp_selector 8 0 40 | act_prof_add_member_to_group IngressPipeImpl.ecmp_selector 9 0 41 | # 42 | # add an entry to the table with the ecmp selector implementation 43 | # table_indirect_add_with_group <[match keys]> => 44 | table_indirect_add_with_group IngressPipeImpl.srv6_encap_v4 0x2 1.1.1.1/32 => 0 45 | # 46 | ############# ROUTING v6 with ECMP 47 | # 48 | # create a group for the routing_v6 ecmp selector 49 | act_prof_create_group IngressPipeImpl.ip6_ecmp_selector 50 | # 51 | # create members for the ecmp selector group (last two arguments are action parameters) 52 | act_prof_create_member IngressPipeImpl.ip6_ecmp_selector IngressPipeImpl.set_next_hop 22:22:22:22:22:22 53 | act_prof_create_member IngressPipeImpl.ip6_ecmp_selector IngressPipeImpl.set_next_hop 33:33:33:33:33:33 54 | # 55 | # add the created members to the group 56 | # act_prof_add_member_to_group 57 | act_prof_add_member_to_group IngressPipeImpl.ip6_ecmp_selector 0 0 58 | act_prof_add_member_to_group IngressPipeImpl.ip6_ecmp_selector 1 0 59 | # 60 | # add an entry to the table with the ecmp selector implementation 61 | # table_indirect_add_with_group
<[match keys]> => 62 | table_indirect_add_with_group IngressPipeImpl.routing_v6 0xfcbbbbbb000100000000000000000000/48 => 0 63 | table_indirect_add_with_group IngressPipeImpl.routing_v6 0xfcbbbbbb000a00000000000000000000/48 => 0 64 | # 65 | ############# UNICAST FIB TABLE 66 | # 67 | table_add IngressPipeImpl.unicast IngressPipeImpl.set_output_port 22:22:22:22:22:22 => 1 68 | table_add IngressPipeImpl.unicast IngressPipeImpl.set_output_port 33:33:33:33:33:33 => 1 69 | # 70 | ############# L2 switch mac address table 71 | # 72 | table_add IngressPipeImpl.l2_firewall NoAction 22:22:22:22:22:22 0 73 | -------------------------------------------------------------------------------- /mininet/bmv2.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netgroup/p4-srv6/0cf815495da462726c661401d0c19151f303d50b/mininet/bmv2.pyc -------------------------------------------------------------------------------- /mininet/ciao.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netgroup/p4-srv6/0cf815495da462726c661401d0c19151f303d50b/mininet/ciao.pcap -------------------------------------------------------------------------------- /mininet/host6.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from mininet.node import Host 16 | 17 | 18 | class IPv6Host(Host): 19 | 20 | def config(self, ipv6, ipv6_gw=None, **params): 21 | super(IPv6Host, self).config(**params) 22 | self.cmd('ip -4 addr flush dev %s' % self.defaultIntf()) 23 | self.cmd('ip -6 addr flush dev %s' % self.defaultIntf()) 24 | self.cmd('ip -6 addr add %s dev %s' % (ipv6, self.defaultIntf())) 25 | if ipv6_gw: 26 | self.cmd('ip -6 route add default via %s' % ipv6_gw) 27 | 28 | def updateIP(): 29 | return ipv6.split('/')[0] 30 | self.defaultIntf().updateIP = updateIP 31 | 32 | def terminate(self): 33 | # self.cmd( 'sysctl -w net.ipv6.conf.all.forwarding=0' ) 34 | super(IPv6Host, self).terminate() 35 | 36 | 37 | class SRv6Host(IPv6Host): 38 | 39 | def config(self, ipv6, ipv6_gw=None, **params): 40 | super(IPv6Host, self).config(**params) 41 | # Enable SRv6 42 | self.cmd('sysctl -w net.ipv6.conf.all.seg6_enabled=1') 43 | self.cmd('sysctl -w net.ipv6.conf.%s.seg6_enabled=1' % self.defaultIntf()) 44 | -------------------------------------------------------------------------------- /mininet/host6.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netgroup/p4-srv6/0cf815495da462726c661401d0c19151f303d50b/mininet/host6.pyc -------------------------------------------------------------------------------- /mininet/ipv6_sr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from mininet.topo import Topo 4 | from mininet.net import Mininet 5 | from mininet.node import Node 6 | from mininet.log import setLogLevel, info 7 | from mininet.cli import CLI 8 | 9 | class IPv6Node( Node ): 10 | 11 | def config( self, ipv6, ipv6_gw=None, **params ): 12 | super( IPv6Node, self).config( **params ) 13 | self.cmd( 'ip -6 addr add %s dev %s' % ( ipv6, self.defaultIntf() ) ) 14 | if ipv6_gw: 15 | self.cmd( 'ip -6 route add default via %s' % ( ipv6_gw ) ) 16 | # Enable SRv6 17 | self.cmd( 'sysctl -w net.ipv6.conf.all.seg6_enabled=1' ) 18 | self.cmd( 'sysctl -w net.ipv6.conf.%s.seg6_enabled=1' % self.defaultIntf() ) 19 | # Enable forwarding on the router: 20 | #self.cmd( 'sysctl -w net.ipv6.conf.all.forwarding=1' ) 21 | 22 | def terminate( self ): 23 | #self.cmd( 'sysctl -w net.ipv6.conf.all.forwarding=0' ) 24 | super( IPv6Node, self ).terminate() 25 | 26 | 27 | class NetworkTopo( Topo ): 28 | "A LinuxRouter connecting three IP subnets" 29 | 30 | def build( self, **_opts ): 31 | s1 = self.addSwitch( 's1' ) 32 | 33 | h1 = self.addHost( 'h1', cls=IPv6Node, ipv6='2001::1/64', ipv6_gw='2001::ff' ) 34 | h2 = self.addHost( 'h2', cls=IPv6Node, ipv6='2001::2/64' ) 35 | 36 | for h, s in [ (h1, s1), (h2, s1) ]: 37 | self.addLink( h, s ) 38 | 39 | def run(): 40 | topo = NetworkTopo() 41 | net = Mininet( topo=topo ) 42 | net.start() 43 | net['h1'].cmd( 'ip -6 addr add fd00:1::1 dev h1-eth0' ) 44 | net['h2'].cmd( 'ip -6 addr add fd00:2::2 dev h2-eth0' ) 45 | net['h1'].cmd( 'ip -6 route add fd00:2::2 encap seg6 mode inline segs 2001::2 dev h1-eth0' ) 46 | net['h2'].cmd( 'ip -6 route add fd00:1::1 encap seg6 mode inline segs 2001::1 dev h2-eth0' ) 47 | net['h1'].cmd( 'ip addr add 1.0.0.1 dev h1-eth0' ) 48 | net['h2'].cmd( 'ip addr add 2.0.0.2 dev h2-eth0' ) 49 | net['h1'].cmd( 'ip route add 2.0.0.2 encap seg6 mode encap segs 2001::2 dev h1-eth0 src 1.0.0.1' ) 50 | net['h2'].cmd( 'ip route add 1.0.0.1 encap seg6 mode encap segs 2001::1 dev h2-eth0 src 2.0.0.2' ) 51 | print 'h1 routing table:' 52 | print net['h1'].cmd( 'ip -6 route' ) 53 | print 'h2 routing table:' 54 | print net['h2'].cmd( 'ip -6 route' ) 55 | CLI( net ) 56 | net.stop() 57 | 58 | if __name__ == '__main__': 59 | setLogLevel( 'info' ) 60 | run() 61 | 62 | -------------------------------------------------------------------------------- /mininet/topo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2019-present Open Networking Foundation 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import argparse 18 | 19 | from mininet.cli import CLI 20 | from mininet.log import setLogLevel 21 | from mininet.net import Mininet 22 | from mininet.node import RemoteController 23 | from mininet.topo import Topo 24 | 25 | from stratum import StratumBmv2Switch 26 | from host6 import IPv6Host 27 | 28 | CPU_PORT = 255 29 | 30 | 31 | class TutorialTopo(Topo): 32 | 33 | """ 34 | /--------\ /----\ /----\ /----\ /----\ 35 | | Site A |---| R1 |---| R4 |---| R5 |---| R8 | 36 | \________/ \____/ \____/ \____/ \____/ 37 | | | | | 38 | \ / | | 39 | R11 R10 R9 40 | / \ 41 | | 42 | /--------\ /----\ /----\ /----\ /----\ 43 | | Site B |---| R2 |---| R3 |---| R6 |---| R7 | 44 | \________/ \____/ \____/ \____/ \____/ 45 | 46 | """ 47 | 48 | 49 | def __init__(self, *args, **kwargs): 50 | Topo.__init__(self, *args, **kwargs) 51 | 52 | # End routers 53 | r1 = self.addSwitch('r1', cls=StratumBmv2Switch,cpuport=CPU_PORT) 54 | r2 = self.addSwitch('r2', cls=StratumBmv2Switch,cpuport=CPU_PORT) 55 | 56 | # Transit routers 57 | r3 = self.addSwitch('r3', cls=StratumBmv2Switch, cpuport=CPU_PORT) 58 | r4 = self.addSwitch('r4', cls=StratumBmv2Switch, cpuport=CPU_PORT) 59 | r5 = self.addSwitch('r5', cls=StratumBmv2Switch, cpuport=CPU_PORT) 60 | r6 = self.addSwitch('r6', cls=StratumBmv2Switch, cpuport=CPU_PORT) 61 | r7 = self.addSwitch('r7', cls=StratumBmv2Switch, cpuport=CPU_PORT) 62 | r8 = self.addSwitch('r8', cls=StratumBmv2Switch, cpuport=CPU_PORT) 63 | r9 = self.addSwitch('r9', cls=StratumBmv2Switch, cpuport=CPU_PORT) 64 | r10 = self.addSwitch('r10', cls=StratumBmv2Switch, cpuport=CPU_PORT) 65 | r11 = self.addSwitch('r11', cls=StratumBmv2Switch, cpuport=CPU_PORT) 66 | r12 = self.addSwitch('r12', cls=StratumBmv2Switch, cpuport=CPU_PORT) 67 | r13 = self.addSwitch('r13', cls=StratumBmv2Switch, cpuport=CPU_PORT) 68 | r14 = self.addSwitch('r14', cls=StratumBmv2Switch, cpuport=CPU_PORT) 69 | 70 | 71 | # Switch Links 72 | self.addLink(r1, r4) 73 | self.addLink(r1, r9) 74 | 75 | self.addLink(r2, r3) 76 | self.addLink(r2, r14) 77 | 78 | self.addLink(r9, r4) 79 | self.addLink(r9, r10) 80 | self.addLink(r9, r13) 81 | self.addLink(r9, r14) 82 | 83 | self.addLink(r14, r10) 84 | self.addLink(r14, r3) 85 | self.addLink(r14, r13) 86 | 87 | self.addLink(r4, r5) 88 | self.addLink(r4, r10) 89 | 90 | self.addLink(r3, r13) 91 | self.addLink(r3, r6) 92 | 93 | self.addLink(r10, r5) 94 | self.addLink(r10, r11) 95 | self.addLink(r10, r12) 96 | self.addLink(r10, r13) 97 | 98 | self.addLink(r13, r11) 99 | self.addLink(r13, r12) 100 | self.addLink(r13, r6) 101 | 102 | self.addLink(r5, r8) 103 | self.addLink(r5, r11) 104 | 105 | self.addLink(r6, r7) 106 | self.addLink(r6, r12) 107 | 108 | self.addLink(r11, r8) 109 | self.addLink(r11, r12) 110 | 111 | self.addLink(r12, r7) 112 | 113 | self.addLink(r8, r7) 114 | 115 | 116 | # IPv6 hosts attached to leaf 1 117 | h1 = self.addHost('h1', cls=IPv6Host, mac="00:00:00:00:00:10", 118 | ipv6='2001:1:1::1/64', ipv6_gw='2001:1:1::ff') 119 | h2 = self.addHost('h2', cls=IPv6Host, mac="00:00:00:00:00:20", 120 | ipv6='2001:1:2::1/64', ipv6_gw='2001:1:2::ff') 121 | 122 | self.addLink(h1, r1) 123 | self.addLink(h2, r2) 124 | 125 | 126 | def main(): 127 | topo = TutorialTopo() 128 | controller = RemoteController('c0', ip="127.0.0.1") 129 | 130 | net = Mininet(topo=topo, controller=None) 131 | net.addController(controller) 132 | 133 | net.start() 134 | CLI(net) 135 | net.stop() 136 | 137 | 138 | if __name__ == "__main__": 139 | main() 140 | -------------------------------------------------------------------------------- /p4src/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /p4src/Makefile: -------------------------------------------------------------------------------- 1 | OUT_DIR=./build 2 | P4C_BM_EXE=p4c-bm2-ss 3 | BMV2_CPU_PORT=255 4 | BMV2_PP_FLAGS=-DTARGET_BMV2 -DCPU_PORT=${BMV2_CPU_PORT} 5 | 6 | all: build 7 | 8 | build: main.p4 9 | $(info ************ COMPILING P4 PROGRAM ************) 10 | mkdir -p $(OUT_DIR) 11 | $(P4C_BM_EXE) --arch v1model -o $(OUT_DIR)/bmv2.json \ 12 | $(BMV2_PP_FLAGS) $(OTHER_PP_FLAGS) \ 13 | --p4runtime-files $(OUT_DIR)/p4info.txt \ 14 | main.p4 15 | 16 | clean: 17 | rm -rf ./build 18 | -------------------------------------------------------------------------------- /p4src/include/checksum.p4: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __CHECKSUM__ 18 | #define __CHECKSUM__ 19 | 20 | control ComputeChecksumImpl(inout parsed_headers_t hdr, 21 | inout local_metadata_t meta) 22 | { 23 | apply { 24 | update_checksum(hdr.ndp.isValid(), 25 | { 26 | hdr.ipv6.src_addr, 27 | hdr.ipv6.dst_addr, 28 | hdr.ipv6.payload_len, 29 | 8w0, 30 | hdr.ipv6.next_hdr, 31 | hdr.icmpv6.type, 32 | hdr.icmpv6.code, 33 | hdr.ndp.flags, 34 | hdr.ndp.target_addr, 35 | hdr.ndp_option.type, 36 | hdr.ndp_option.length, 37 | hdr.ndp_option.value 38 | }, 39 | hdr.icmpv6.checksum, 40 | HashAlgorithm.csum16 41 | ); 42 | 43 | update_checksum(meta.ipv4_update, 44 | { 45 | hdr.ipv4.version, 46 | hdr.ipv4.ihl, 47 | hdr.ipv4.dscp, 48 | hdr.ipv4.ecn, 49 | hdr.ipv4.total_len, 50 | hdr.ipv4.identification, 51 | hdr.ipv4.flags, 52 | hdr.ipv4.frag_offset, 53 | hdr.ipv4.ttl, 54 | hdr.ipv4.protocol, 55 | 16w0, 56 | hdr.ipv4.src_addr, 57 | hdr.ipv4.dst_addr 58 | }, 59 | hdr.ipv4.hdr_checksum, 60 | HashAlgorithm.csum16 61 | ); 62 | 63 | } 64 | } 65 | 66 | control VerifyChecksumImpl(inout parsed_headers_t hdr, 67 | inout local_metadata_t meta) 68 | { 69 | apply {} 70 | } 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /p4src/include/define.p4: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __DEFINE__ 18 | #define __DEFINE__ 19 | 20 | typedef bit<9> port_num_t; 21 | typedef bit<48> mac_addr_t; 22 | typedef bit<16> group_id_t; 23 | typedef bit<32> ipv4_addr_t; 24 | typedef bit<128> ipv6_addr_t; 25 | typedef bit<16> l4_port_t; 26 | 27 | const bit<16> ETHERTYPE_IPV4 = 0x0800; 28 | const bit<16> ETHERTYPE_IPV6 = 0x86dd; 29 | const bit<16> ETHERTYPE_ARP = 0x0806; 30 | 31 | const bit<8> PROTO_ICMP = 1; 32 | const bit<8> PROTO_TCP = 6; 33 | const bit<8> PROTO_UDP = 17; 34 | const bit<8> PROTO_SRV6 = 43; 35 | const bit<8> PROTO_ICMPV6 = 58; 36 | const bit<8> PROTO_IPV6 = 41; 37 | const bit<8> PROTO_IP_IN_IP = 4; 38 | 39 | const bit<8> ICMP6_TYPE_NS = 135; 40 | const bit<8> ICMP6_TYPE_NA = 136; 41 | const bit<8> NDP_OPT_TARGET_LL_ADDR = 2; 42 | const mac_addr_t IPV6_MCAST_01 = 0x33_33_00_00_00_01; 43 | const bit<32> NDP_FLAG_ROUTER = 0x80000000; 44 | const bit<32> NDP_FLAG_SOLICITED = 0x40000000; 45 | const bit<32> NDP_FLAG_OVERRIDE = 0x20000000; 46 | #endif 47 | -------------------------------------------------------------------------------- /p4src/include/header.p4: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __HEADER__ 18 | #define __HEADER__ 19 | 20 | #include "define.p4" 21 | 22 | #define MAX_HOPS 4 23 | 24 | @controller_header("packet_in") 25 | header packet_in_header_t { 26 | port_num_t ingress_port; 27 | bit<7> _pad; 28 | } 29 | 30 | @controller_header("packet_out") 31 | header packet_out_header_t { 32 | port_num_t egress_port; 33 | bit<7> _pad; 34 | } 35 | 36 | header ethernet_t { 37 | mac_addr_t dst_addr; 38 | mac_addr_t src_addr; 39 | bit<16> ether_type; 40 | } 41 | 42 | header ipv4_t { 43 | bit<4> version; 44 | bit<4> ihl; 45 | bit<6> dscp; 46 | bit<2> ecn; 47 | bit<16> total_len; 48 | bit<16> identification; 49 | bit<3> flags; 50 | bit<13> frag_offset; 51 | bit<8> ttl; 52 | bit<8> protocol; 53 | bit<16> hdr_checksum; 54 | bit<32> src_addr; 55 | bit<32> dst_addr; 56 | } 57 | 58 | header ipv6_t { 59 | bit<4> version; 60 | bit<8> traffic_class; 61 | bit<20> flow_label; 62 | bit<16> payload_len; 63 | bit<8> next_hdr; 64 | bit<8> hop_limit; 65 | bit<128> src_addr; 66 | bit<128> dst_addr; 67 | } 68 | 69 | header srv6h_t { 70 | bit<8> next_hdr; 71 | bit<8> hdr_ext_len; 72 | bit<8> routing_type; 73 | bit<8> segment_left; 74 | bit<8> last_entry; 75 | bit<8> flags; 76 | bit<16> tag; 77 | } 78 | 79 | header srv6_list_t { 80 | bit<128> segment_id; 81 | } 82 | 83 | header arp_t { 84 | bit<16> hw_type; 85 | bit<16> proto_type; 86 | bit<8> hw_addr_len; 87 | bit<8> proto_addr_len; 88 | bit<16> opcode; 89 | } 90 | 91 | header tcp_t { 92 | bit<16> src_port; 93 | bit<16> dst_port; 94 | bit<32> seq_no; 95 | bit<32> ack_no; 96 | bit<4> data_offset; 97 | bit<3> res; 98 | bit<3> ecn; 99 | bit<6> ctrl; 100 | bit<16> window; 101 | bit<16> checksum; 102 | bit<16> urgent_ptr; 103 | } 104 | 105 | header udp_t { 106 | bit<16> src_port; 107 | bit<16> dst_port; 108 | bit<16> len; 109 | bit<16> checksum; 110 | } 111 | 112 | header icmp_t { 113 | bit<8> type; 114 | bit<8> icmp_code; 115 | bit<16> checksum; 116 | bit<16> identifier; 117 | bit<16> sequence_number; 118 | bit<64> timestamp; 119 | } 120 | 121 | header icmpv6_t { 122 | bit<8> type; 123 | bit<8> code; 124 | bit<16> checksum; 125 | } 126 | 127 | header ndp_t { 128 | bit<32> flags; 129 | bit<128> target_addr; 130 | } 131 | 132 | header ndp_option_t { 133 | bit<8> type; 134 | bit<8> length; 135 | bit<48> value; 136 | } 137 | 138 | //Custom metadata definition 139 | struct local_metadata_t { 140 | bool is_multicast; 141 | bool skip_l2; 142 | bool xconnect; 143 | ipv6_addr_t next_srv6_sid; 144 | ipv6_addr_t ua_next_hop; 145 | bit<8> ip_proto; 146 | bit<8> icmp_type; 147 | l4_port_t l4_src_port; 148 | l4_port_t l4_dst_port; 149 | bool ipv4_update; 150 | } 151 | 152 | struct parsed_headers_t { 153 | ethernet_t ethernet; 154 | ipv6_t ipv6; 155 | ipv6_t ipv6_inner; 156 | ipv4_t ipv4; 157 | srv6h_t srv6h; 158 | srv6_list_t[MAX_HOPS] srv6_list; 159 | arp_t arp; 160 | tcp_t tcp; 161 | udp_t udp; 162 | icmp_t icmp; 163 | icmpv6_t icmpv6; 164 | ndp_t ndp; 165 | ndp_option_t ndp_option; 166 | packet_out_header_t packet_out; 167 | packet_in_header_t packet_in; 168 | } 169 | 170 | #endif 171 | -------------------------------------------------------------------------------- /p4src/include/parser.p4: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __PARSER__ 18 | #define __PARSER__ 19 | 20 | #include "define.p4" 21 | 22 | parser ParserImpl (packet_in packet, 23 | out parsed_headers_t hdr, 24 | inout local_metadata_t local_metadata, 25 | inout standard_metadata_t standard_metadata) 26 | { 27 | state start { 28 | transition select(standard_metadata.ingress_port) { 29 | CPU_PORT: parse_packet_out; 30 | default: parse_ethernet; 31 | } 32 | } 33 | 34 | state parse_packet_out { 35 | packet.extract(hdr.packet_out); 36 | transition parse_ethernet; 37 | } 38 | 39 | state parse_ethernet { 40 | packet.extract(hdr.ethernet); 41 | transition select(hdr.ethernet.ether_type){ 42 | ETHERTYPE_ARP: parse_arp; 43 | ETHERTYPE_IPV4: parse_ipv4; 44 | ETHERTYPE_IPV6: parse_ipv6; 45 | default: accept; 46 | } 47 | } 48 | 49 | state parse_ipv6 { 50 | packet.extract(hdr.ipv6); 51 | local_metadata.ip_proto = hdr.ipv6.next_hdr; 52 | transition select(hdr.ipv6.next_hdr) { 53 | PROTO_TCP: parse_tcp; 54 | PROTO_UDP: parse_udp; 55 | PROTO_ICMPV6: parse_icmpv6; 56 | PROTO_SRV6: parse_srv6; 57 | PROTO_IPV6: parse_ipv6_inner; 58 | PROTO_IP_IN_IP: parse_ipv4; 59 | default: accept; 60 | } 61 | } 62 | 63 | state parse_srv6 { 64 | packet.extract(hdr.srv6h); 65 | transition parse_srv6_list; 66 | } 67 | state parse_srv6_list { 68 | packet.extract(hdr.srv6_list.next); 69 | bool next_segment = (bit<32>)hdr.srv6h.segment_left - 1 == (bit<32>)hdr.srv6_list.lastIndex; 70 | transition select(next_segment) { 71 | true: mark_current_srv6; 72 | _: check_last_srv6; 73 | } 74 | } 75 | 76 | state mark_current_srv6 { 77 | // current metadata 78 | local_metadata.next_srv6_sid = hdr.srv6_list.last.segment_id; 79 | transition check_last_srv6; 80 | } 81 | 82 | state check_last_srv6 { 83 | // working with bit<8> and int<32> which cannot be cast directly; using bit<32> as common intermediate type for comparision 84 | bool last_segment = (bit<32>)hdr.srv6h.last_entry == (bit<32>)hdr.srv6_list.lastIndex; 85 | transition select(last_segment) { 86 | true: parse_srv6_next_hdr; 87 | false: parse_srv6_list; 88 | } 89 | } 90 | state parse_srv6_next_hdr { 91 | transition select(hdr.srv6h.next_hdr) { 92 | PROTO_TCP: parse_tcp; 93 | PROTO_UDP: parse_udp; 94 | PROTO_ICMPV6: parse_icmpv6; 95 | PROTO_IPV6: parse_ipv6_inner; 96 | PROTO_IP_IN_IP: parse_ipv4; 97 | default: accept; 98 | } 99 | } 100 | 101 | state parse_ipv4 { 102 | packet.extract(hdr.ipv4); 103 | local_metadata.ip_proto = hdr.ipv4.protocol; 104 | //Need header verification? 105 | transition select(hdr.ipv4.protocol) { 106 | PROTO_TCP: parse_tcp; 107 | PROTO_UDP: parse_udp; 108 | PROTO_ICMP: parse_icmp; 109 | default: accept; 110 | } 111 | } 112 | 113 | state parse_ipv6_inner { 114 | packet.extract(hdr.ipv6_inner); 115 | 116 | transition select(hdr.ipv6_inner.next_hdr) { 117 | PROTO_TCP: parse_tcp; 118 | PROTO_UDP: parse_udp; 119 | PROTO_ICMPV6: parse_icmpv6; 120 | PROTO_SRV6: parse_srv6; 121 | default: accept; 122 | } 123 | } 124 | 125 | state parse_arp { 126 | packet.extract(hdr.arp); 127 | transition accept; 128 | } 129 | 130 | state parse_tcp { 131 | packet.extract(hdr.tcp); 132 | local_metadata.l4_src_port = hdr.tcp.src_port; 133 | local_metadata.l4_dst_port = hdr.tcp.dst_port; 134 | transition accept; 135 | } 136 | 137 | state parse_udp { 138 | packet.extract(hdr.udp); 139 | local_metadata.l4_src_port = hdr.udp.src_port; 140 | local_metadata.l4_dst_port = hdr.udp.dst_port; 141 | transition accept; 142 | } 143 | 144 | state parse_icmp { 145 | packet.extract(hdr.icmp); 146 | local_metadata.icmp_type = hdr.icmp.type; 147 | transition accept; 148 | } 149 | 150 | state parse_icmpv6 { 151 | packet.extract(hdr.icmpv6); 152 | local_metadata.icmp_type = hdr.icmpv6.type; 153 | transition select(hdr.icmpv6.type) { 154 | ICMP6_TYPE_NS: parse_ndp; 155 | ICMP6_TYPE_NA: parse_ndp; 156 | default: accept; 157 | } 158 | 159 | } 160 | 161 | state parse_ndp { 162 | packet.extract(hdr.ndp); 163 | transition parse_ndp_option; 164 | } 165 | 166 | state parse_ndp_option { 167 | packet.extract(hdr.ndp_option); 168 | transition accept; 169 | } 170 | } 171 | 172 | control DeparserImpl(packet_out packet, in parsed_headers_t hdr) { 173 | apply { 174 | packet.emit(hdr.packet_in); 175 | packet.emit(hdr.ethernet); 176 | packet.emit(hdr.arp); 177 | packet.emit(hdr.ipv6); 178 | packet.emit(hdr.srv6h); 179 | packet.emit(hdr.srv6_list); 180 | packet.emit(hdr.ipv6_inner); 181 | packet.emit(hdr.ipv4); 182 | packet.emit(hdr.tcp); 183 | packet.emit(hdr.udp); 184 | packet.emit(hdr.icmp); 185 | packet.emit(hdr.icmpv6); 186 | packet.emit(hdr.ndp); 187 | packet.emit(hdr.ndp_option); 188 | } 189 | } 190 | 191 | #endif 192 | -------------------------------------------------------------------------------- /p4src/main.p4: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Open Networking Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include "include/header.p4" 21 | #include "include/parser.p4" 22 | #include "include/checksum.p4" 23 | 24 | #define CPU_CLONE_SESSION_ID 99 25 | #define UN_BLOCK_MASK 0xffffffff000000000000000000000000 26 | 27 | 28 | control IngressPipeImpl (inout parsed_headers_t hdr, 29 | inout local_metadata_t local_metadata, 30 | inout standard_metadata_t standard_metadata) { 31 | 32 | action drop() { 33 | mark_to_drop(standard_metadata); 34 | } 35 | 36 | action set_output_port(port_num_t port_num) { 37 | standard_metadata.egress_spec = port_num; 38 | } 39 | action set_multicast_group(group_id_t gid) { 40 | standard_metadata.mcast_grp = gid; 41 | local_metadata.is_multicast = true; 42 | } 43 | 44 | direct_counter(CounterType.packets_and_bytes) unicast_counter; 45 | table unicast { 46 | key = { 47 | hdr.ethernet.dst_addr: exact; 48 | } 49 | actions = { 50 | set_output_port; 51 | drop; 52 | NoAction; 53 | } 54 | counters = unicast_counter; 55 | default_action = NoAction(); 56 | } 57 | 58 | direct_counter(CounterType.packets_and_bytes) multicast_counter; 59 | table multicast { 60 | key = { 61 | hdr.ethernet.dst_addr: ternary; 62 | } 63 | actions = { 64 | set_multicast_group; 65 | drop; 66 | } 67 | counters = multicast_counter; 68 | const default_action = drop; 69 | } 70 | 71 | direct_counter(CounterType.packets_and_bytes) l2_firewall_counter; 72 | table l2_firewall { 73 | key = { 74 | hdr.ethernet.dst_addr: exact; 75 | } 76 | actions = { 77 | NoAction; 78 | } 79 | counters = l2_firewall_counter; 80 | } 81 | 82 | action set_next_hop(mac_addr_t next_hop) { 83 | hdr.ethernet.src_addr = hdr.ethernet.dst_addr; 84 | hdr.ethernet.dst_addr = next_hop; 85 | hdr.ipv6.hop_limit = hdr.ipv6.hop_limit - 1; 86 | } 87 | 88 | // TODO: implement ecmp with ipv6.src+ipv6.dst+ipv6.flow_label 89 | action_selector(HashAlgorithm.crc16, 32w64, 32w10) ip6_ecmp_selector; 90 | direct_counter(CounterType.packets_and_bytes) routing_v6_counter; 91 | table routing_v6 { 92 | key = { 93 | hdr.ipv6.dst_addr: lpm; 94 | 95 | hdr.ipv6.flow_label : selector; 96 | hdr.ipv6.dst_addr : selector; 97 | hdr.ipv6.src_addr : selector; 98 | } 99 | actions = { 100 | set_next_hop; 101 | } 102 | counters = routing_v6_counter; 103 | implementation = ip6_ecmp_selector; 104 | } 105 | 106 | // TODO calc checksum 107 | action set_next_hop_v4(mac_addr_t next_hop) { 108 | hdr.ethernet.src_addr = hdr.ethernet.dst_addr; 109 | hdr.ethernet.dst_addr = next_hop; 110 | hdr.ipv4.ttl = hdr.ipv4.ttl - 1; 111 | local_metadata.ipv4_update = true; 112 | } 113 | 114 | direct_counter(CounterType.packets_and_bytes) routing_v4_counter; 115 | table routing_v4 { 116 | key = { 117 | hdr.ipv4.dst_addr: lpm; 118 | } 119 | actions = { 120 | set_next_hop_v4; 121 | } 122 | counters = routing_v4_counter; 123 | } 124 | 125 | /* 126 | * NDP reply table and actions. 127 | * Handles NDP router solicitation message and send router advertisement to the sender. 128 | */ 129 | action ndp_ns_to_na(mac_addr_t target_mac) { 130 | hdr.ethernet.src_addr = target_mac; 131 | hdr.ethernet.dst_addr = IPV6_MCAST_01; 132 | bit<128> host_ipv6_tmp = hdr.ipv6.src_addr; 133 | hdr.ipv6.src_addr = hdr.ndp.target_addr; 134 | hdr.ipv6.dst_addr = host_ipv6_tmp; 135 | hdr.icmpv6.type = ICMP6_TYPE_NA; 136 | hdr.ndp.flags = NDP_FLAG_ROUTER | NDP_FLAG_OVERRIDE; 137 | hdr.ndp_option.setValid(); 138 | hdr.ndp_option.type = NDP_OPT_TARGET_LL_ADDR; 139 | hdr.ndp_option.length = 1; 140 | hdr.ndp_option.value = target_mac; 141 | hdr.ipv6.next_hdr = PROTO_ICMPV6; 142 | standard_metadata.egress_spec = standard_metadata.ingress_port; 143 | local_metadata.skip_l2 = true; 144 | } 145 | 146 | direct_counter(CounterType.packets_and_bytes) ndp_reply_table_counter; 147 | table ndp_reply_table { 148 | key = { 149 | hdr.ndp.target_addr: exact; 150 | } 151 | actions = { 152 | ndp_ns_to_na; 153 | } 154 | counters = ndp_reply_table_counter; 155 | } 156 | 157 | action srv6_end() {} 158 | 159 | action srv6_usid_un() { 160 | hdr.ipv6.dst_addr = (hdr.ipv6.dst_addr & UN_BLOCK_MASK) | ((hdr.ipv6.dst_addr << 16) & ~((bit<128>)UN_BLOCK_MASK)); 161 | } 162 | 163 | action srv6_usid_ua(ipv6_addr_t next_hop) { 164 | hdr.ipv6.dst_addr = (hdr.ipv6.dst_addr & UN_BLOCK_MASK) | ((hdr.ipv6.dst_addr << 32) & ~((bit<128>)UN_BLOCK_MASK)); 165 | local_metadata.xconnect = true; 166 | 167 | local_metadata.ua_next_hop = next_hop; 168 | } 169 | 170 | action srv6_end_x(ipv6_addr_t next_hop) { 171 | hdr.ipv6.dst_addr = (hdr.ipv6.dst_addr & UN_BLOCK_MASK) | ((hdr.ipv6.dst_addr << 32) & ~((bit<128>)UN_BLOCK_MASK)); 172 | local_metadata.xconnect = true; 173 | 174 | local_metadata.ua_next_hop = next_hop; 175 | } 176 | 177 | action srv6_end_dx6() { 178 | hdr.ipv6.version = hdr.ipv6_inner.version; 179 | hdr.ipv6.traffic_class = hdr.ipv6_inner.traffic_class; 180 | hdr.ipv6.flow_label = hdr.ipv6_inner.flow_label; 181 | hdr.ipv6.payload_len = hdr.ipv6_inner.payload_len; 182 | hdr.ipv6.next_hdr = hdr.ipv6_inner.next_hdr; 183 | hdr.ipv6.hop_limit = hdr.ipv6_inner.hop_limit; 184 | hdr.ipv6.src_addr = hdr.ipv6_inner.src_addr; 185 | hdr.ipv6.dst_addr = hdr.ipv6_inner.dst_addr; 186 | 187 | hdr.ipv6_inner.setInvalid(); 188 | hdr.srv6h.setInvalid(); 189 | hdr.srv6_list[0].setInvalid(); 190 | } 191 | 192 | action srv6_end_dx4() { 193 | hdr.srv6_list[0].setInvalid(); 194 | hdr.srv6h.setInvalid(); 195 | hdr.ipv6.setInvalid(); 196 | hdr.ipv6_inner.setInvalid(); 197 | 198 | hdr.ethernet.ether_type = ETHERTYPE_IPV4; 199 | } 200 | 201 | direct_counter(CounterType.packets_and_bytes) srv6_localsid_table_counter; 202 | table srv6_localsid_table { 203 | key = { 204 | hdr.ipv6.dst_addr: lpm; 205 | } 206 | actions = { 207 | srv6_end; 208 | srv6_end_x; 209 | srv6_end_dx6; 210 | srv6_end_dx4; 211 | srv6_usid_un; 212 | srv6_usid_ua; 213 | NoAction; 214 | } 215 | default_action = NoAction; 216 | counters = srv6_localsid_table_counter; 217 | } 218 | 219 | action xconnect_act(mac_addr_t next_hop) { 220 | hdr.ethernet.src_addr = hdr.ethernet.dst_addr; 221 | hdr.ethernet.dst_addr = next_hop; 222 | } 223 | 224 | direct_counter(CounterType.packets_and_bytes) xconnect_table_counter; 225 | table xconnect_table { 226 | key = { 227 | local_metadata.ua_next_hop: lpm; 228 | } 229 | actions = { 230 | xconnect_act; 231 | NoAction; 232 | } 233 | default_action = NoAction; 234 | counters = xconnect_table_counter; 235 | } 236 | 237 | action usid_encap_1(ipv6_addr_t src_addr, ipv6_addr_t s1) { 238 | hdr.ipv6_inner.setValid(); 239 | 240 | hdr.ipv6_inner.version = 6; 241 | hdr.ipv6_inner.traffic_class = hdr.ipv6.traffic_class; 242 | hdr.ipv6_inner.flow_label = hdr.ipv6.flow_label; 243 | hdr.ipv6_inner.payload_len = hdr.ipv6.payload_len; 244 | hdr.ipv6_inner.next_hdr = hdr.ipv6.next_hdr; 245 | hdr.ipv6_inner.hop_limit = hdr.ipv6.hop_limit; 246 | hdr.ipv6_inner.src_addr = hdr.ipv6.src_addr; 247 | hdr.ipv6_inner.dst_addr = hdr.ipv6.dst_addr; 248 | 249 | hdr.ipv6.payload_len = hdr.ipv6.payload_len + 40; 250 | hdr.ipv6.next_hdr = PROTO_IPV6; 251 | hdr.ipv6.src_addr = src_addr; 252 | hdr.ipv6.dst_addr = s1; 253 | } 254 | 255 | action usid_encap_2(ipv6_addr_t src_addr, ipv6_addr_t s1, ipv6_addr_t s2) { 256 | hdr.ipv6_inner.setValid(); 257 | 258 | hdr.ipv6_inner.version = 6; 259 | hdr.ipv6_inner.traffic_class = hdr.ipv6.traffic_class; 260 | hdr.ipv6_inner.flow_label = hdr.ipv6.flow_label; 261 | hdr.ipv6_inner.payload_len = hdr.ipv6.payload_len; 262 | hdr.ipv6_inner.next_hdr = hdr.ipv6.next_hdr; 263 | hdr.ipv6_inner.hop_limit = hdr.ipv6.hop_limit; 264 | hdr.ipv6_inner.src_addr = hdr.ipv6.src_addr; 265 | hdr.ipv6_inner.dst_addr = hdr.ipv6.dst_addr; 266 | 267 | hdr.ipv6.payload_len = hdr.ipv6.payload_len + 40 + 24; 268 | hdr.ipv6.next_hdr = PROTO_SRV6; 269 | hdr.ipv6.src_addr = src_addr; 270 | hdr.ipv6.dst_addr = s1; 271 | 272 | hdr.srv6h.setValid(); 273 | hdr.srv6h.next_hdr = PROTO_IPV6; 274 | hdr.srv6h.hdr_ext_len = 0x2; 275 | hdr.srv6h.routing_type = 0x4; 276 | hdr.srv6h.segment_left = 0; 277 | hdr.srv6h.last_entry = 0; 278 | hdr.srv6h.flags = 0; 279 | hdr.srv6h.tag = 0; 280 | 281 | hdr.srv6_list[0].setValid(); 282 | hdr.srv6_list[0].segment_id = s2; 283 | } 284 | 285 | direct_counter(CounterType.packets_and_bytes) srv6_encap_table_counter; 286 | table srv6_encap { 287 | key = { 288 | hdr.ipv6.dst_addr: lpm; 289 | } 290 | actions = { 291 | usid_encap_1; 292 | usid_encap_2; 293 | NoAction; 294 | } 295 | default_action = NoAction; 296 | counters = srv6_encap_table_counter; 297 | } 298 | 299 | action usid_encap_1_v4(ipv6_addr_t src_addr, ipv6_addr_t s1) { 300 | hdr.ipv6.setValid(); 301 | 302 | hdr.ipv6.version = 6; 303 | hdr.ipv6.traffic_class = hdr.ipv4.dscp ++ hdr.ipv4.ecn; 304 | hash(hdr.ipv6.flow_label, 305 | HashAlgorithm.crc32, 306 | (bit<20>) 0, 307 | { 308 | hdr.ipv4.src_addr, 309 | hdr.ipv4.dst_addr, 310 | local_metadata.ip_proto, 311 | local_metadata.l4_src_port, 312 | local_metadata.l4_dst_port 313 | }, 314 | (bit<20>) 1048575); 315 | hdr.ipv6.payload_len = hdr.ipv4.total_len; 316 | hdr.ipv6.next_hdr = PROTO_IP_IN_IP; 317 | hdr.ipv6.hop_limit = hdr.ipv4.ttl; 318 | hdr.ipv6.src_addr = src_addr; 319 | hdr.ipv6.dst_addr = s1; 320 | 321 | hdr.ethernet.ether_type = ETHERTYPE_IPV6; 322 | } 323 | 324 | action usid_encap_2_v4(ipv6_addr_t src_addr, ipv6_addr_t s1, ipv6_addr_t s2) { 325 | hdr.ipv6.setValid(); 326 | 327 | hdr.ipv6.version = 6; 328 | hdr.ipv6.traffic_class = hdr.ipv4.dscp ++ hdr.ipv4.ecn; 329 | hash(hdr.ipv6.flow_label, 330 | HashAlgorithm.crc32, 331 | (bit<20>) 0, 332 | { 333 | hdr.ipv4.src_addr, 334 | hdr.ipv4.dst_addr, 335 | local_metadata.ip_proto, 336 | local_metadata.l4_src_port, 337 | local_metadata.l4_dst_port 338 | }, 339 | (bit<20>) 1048575); 340 | hdr.ipv6.payload_len = hdr.ipv4.total_len + 24; 341 | hdr.ipv6.next_hdr = PROTO_SRV6; 342 | hdr.ipv6.hop_limit = hdr.ipv4.ttl; 343 | hdr.ipv6.src_addr = src_addr; 344 | hdr.ipv6.dst_addr = s1; 345 | 346 | hdr.srv6h.setValid(); 347 | hdr.srv6h.next_hdr = PROTO_IP_IN_IP; 348 | hdr.srv6h.hdr_ext_len = 0x2; 349 | hdr.srv6h.routing_type = 0x4; 350 | hdr.srv6h.segment_left = 0; 351 | hdr.srv6h.last_entry = 0; 352 | hdr.srv6h.flags = 0; 353 | hdr.srv6h.tag = 0; 354 | 355 | hdr.srv6_list[0].setValid(); 356 | hdr.srv6_list[0].segment_id = s2; 357 | 358 | hdr.ethernet.ether_type = ETHERTYPE_IPV6; 359 | } 360 | 361 | // create one group 362 | action_selector(HashAlgorithm.crc16, 32w64, 32w10) ecmp_selector; 363 | direct_counter(CounterType.packets_and_bytes) srv6_encap_v4_table_counter; 364 | table srv6_encap_v4 { 365 | key = { 366 | hdr.ipv4.dscp: exact; 367 | hdr.ipv4.dst_addr: lpm; 368 | 369 | hdr.ipv4.src_addr: selector; 370 | hdr.ipv4.dst_addr: selector; 371 | local_metadata.ip_proto: selector; 372 | local_metadata.l4_src_port: selector; 373 | local_metadata.l4_dst_port: selector; 374 | } 375 | actions = { 376 | usid_encap_1_v4; 377 | usid_encap_2_v4; 378 | NoAction; 379 | } 380 | default_action = NoAction; 381 | implementation = ecmp_selector; 382 | counters = srv6_encap_v4_table_counter; 383 | } 384 | 385 | 386 | /* 387 | * ACL table and actions. 388 | * Clone the packet to the CPU (PacketIn) or drop. 389 | */ 390 | 391 | action clone_to_cpu() { 392 | clone3(CloneType.I2E, CPU_CLONE_SESSION_ID, standard_metadata); 393 | } 394 | 395 | direct_counter(CounterType.packets_and_bytes) acl_counter; 396 | table acl { 397 | key = { 398 | standard_metadata.ingress_port: ternary; 399 | hdr.ethernet.dst_addr: ternary; 400 | hdr.ethernet.src_addr: ternary; 401 | hdr.ethernet.ether_type: ternary; 402 | local_metadata.ip_proto: ternary; 403 | local_metadata.icmp_type: ternary; 404 | local_metadata.l4_src_port: ternary; 405 | local_metadata.l4_dst_port: ternary; 406 | } 407 | actions = { 408 | clone_to_cpu; 409 | drop; 410 | } 411 | counters = acl_counter; 412 | } 413 | 414 | apply { 415 | if (hdr.packet_out.isValid()) { 416 | standard_metadata.egress_spec = hdr.packet_out.egress_port; 417 | hdr.packet_out.setInvalid(); 418 | exit; 419 | } 420 | 421 | if (hdr.icmpv6.isValid() && hdr.icmpv6.type == ICMP6_TYPE_NS) { 422 | ndp_reply_table.apply(); 423 | } 424 | 425 | if (hdr.ipv6.hop_limit == 0) { 426 | drop(); 427 | } 428 | 429 | if (l2_firewall.apply().hit) { 430 | switch(srv6_localsid_table.apply().action_run) { 431 | srv6_end: { 432 | // support for reduced SRH 433 | if (hdr.srv6h.segment_left > 0) { 434 | // set destination IP address to next segment 435 | hdr.ipv6.dst_addr = local_metadata.next_srv6_sid; 436 | // decrement segments left 437 | hdr.srv6h.segment_left = hdr.srv6h.segment_left - 1; 438 | } else { 439 | // set destination IP address to next segment 440 | hdr.ipv6.dst_addr = hdr.srv6_list[0].segment_id; 441 | } 442 | } 443 | srv6_end_dx4: { 444 | routing_v4.apply(); 445 | } 446 | } 447 | 448 | // SRv6 Encapsulation 449 | if (hdr.ipv4.isValid() && !hdr.ipv6.isValid()) { 450 | srv6_encap_v4.apply(); 451 | } else { 452 | srv6_encap.apply(); 453 | } 454 | 455 | if (!local_metadata.xconnect) { 456 | routing_v6.apply(); 457 | } else { 458 | xconnect_table.apply(); 459 | } 460 | } 461 | 462 | if (!local_metadata.skip_l2) { 463 | if (!unicast.apply().hit) { 464 | multicast.apply(); 465 | } 466 | } 467 | 468 | acl.apply(); 469 | 470 | } 471 | } 472 | 473 | control EgressPipeImpl (inout parsed_headers_t hdr, 474 | inout local_metadata_t local_metadata, 475 | inout standard_metadata_t standard_metadata) { 476 | apply { 477 | if (standard_metadata.egress_port == CPU_PORT) { 478 | hdr.packet_in.setValid(); 479 | hdr.packet_in.ingress_port = standard_metadata.ingress_port; 480 | } 481 | 482 | if (local_metadata.is_multicast == true 483 | && standard_metadata.ingress_port == standard_metadata.egress_port) { 484 | mark_to_drop(standard_metadata); 485 | } 486 | } 487 | } 488 | 489 | V1Switch( 490 | ParserImpl(), 491 | VerifyChecksumImpl(), 492 | IngressPipeImpl(), 493 | EgressPipeImpl(), 494 | ComputeChecksumImpl(), 495 | DeparserImpl() 496 | ) main; 497 | -------------------------------------------------------------------------------- /test/ipv4_test_encap_1sid.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netgroup/p4-srv6/0cf815495da462726c661401d0c19151f303d50b/test/ipv4_test_encap_1sid.pcap -------------------------------------------------------------------------------- /test/ipv4_test_encap_2sid.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netgroup/p4-srv6/0cf815495da462726c661401d0c19151f303d50b/test/ipv4_test_encap_2sid.pcap -------------------------------------------------------------------------------- /test/ipv6_test_decap.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netgroup/p4-srv6/0cf815495da462726c661401d0c19151f303d50b/test/ipv6_test_decap.pcap -------------------------------------------------------------------------------- /test/test_dx4.txt: -------------------------------------------------------------------------------- 1 | table_add IngressPipeImpl.l2_firewall NoAction 01:01:01:01:01:01 0 2 | table_add IngressPipeImpl.my_sid_table IngressPipeImpl.srv6_end_dx4 fcbb::1/128 0 3 | table_add IngressPipeImpl.routing_v4 IngressPipeImpl.set_next_hop_v4 10.0.1.1/24 => 02:02:02:02:02:02 4 | table_add IngressPipeImpl.unicast IngressPipeImpl.set_output_port 02:02:02:02:02:02 => 1 5 | 6 | -------------------------------------------------------------------------------- /test/test_encap.txt: -------------------------------------------------------------------------------- 1 | table_add IngressPipeImpl.l2_firewall NoAction 01:01:01:01:01:01 0 2 | table_add IngressPipeImpl.srv6_encap_v4 IngressPipeImpl.usid_encap_1_v4 10.0.2.1/32 => fcbb::1 fcbb::a 3 | table_add IngressPipeImpl.srv6_encap_v4 IngressPipeImpl.usid_encap_2_v4 10.0.2.2/32 => fcbb::1 fcbb::a fcbb::b 4 | table_add IngressPipeImpl.routing_v6 IngressPipeImpl.set_next_hop fcbb::/16 => 02:02:02:02:02:02 5 | table_add IngressPipeImpl.unicast IngressPipeImpl.set_output_port 02:02:02:02:02:02 => 1 6 | 7 | -------------------------------------------------------------------------------- /util/docker/Makefile: -------------------------------------------------------------------------------- 1 | include Makefile.vars 2 | 3 | build: build-stratum_bmv2 build-mvn 4 | push: push-stratum_bmv2 push-mvn 5 | 6 | build-stratum_bmv2: 7 | cd stratum_bmv2 && docker build -t ${STRATUM_BMV2_IMG} . 8 | 9 | build-mvn: 10 | cd ../../app && docker build --squash -f ../util/docker/mvn/Dockerfile \ 11 | -t ${MVN_IMG} . 12 | 13 | push-stratum_bmv2: 14 | # Remember to update Makefile.vars with the new image sha 15 | docker push ${STRATUM_BMV2_IMG} 16 | 17 | push-mvn: 18 | # Remember to update Makefile.vars with the new image sha 19 | docker push ${MVN_IMG} 20 | -------------------------------------------------------------------------------- /util/docker/Makefile.vars: -------------------------------------------------------------------------------- 1 | ONOS_IMG := onosproject/onos:2.2.2 2 | P4RT_SH_IMG := p4lang/p4runtime-sh:latest 3 | P4C_IMG := opennetworking/p4c:stable 4 | STRATUM_BMV2_IMG := opennetworking/ngsdn-tutorial:stratum_bmv2 5 | MVN_IMG := opennetworking/ngsdn-tutorial:mvn 6 | GNMI_CLI_IMG := bocon/gnmi-cli:latest 7 | YANG_IMG := bocon/yang-tools:latest 8 | SSHPASS_IMG := ictu/sshpass 9 | 10 | ONOS_SHA := sha256:438815ab20300cd7a31702b7dea635152c4c4b5b2fed9b14970bd2939a139d2a 11 | P4RT_SH_SHA := sha256:6ae50afb5bde620acb9473ce6cd7b990ff6cc63fe4113cf5584c8e38fe42176c 12 | P4C_SHA := sha256:8f9d27a6edf446c3801db621359fec5de993ebdebc6844d8b1292e369be5dfea 13 | STRATUM_BMV2_SHA := sha256:f31faa5e83abbb2d9cf39d28b3578f6e113225641337ec7d16d867b0667524ef 14 | MVN_SHA := sha256:d85eb93ac909a90f49b16b33cb872620f9b4f640e7a6451859aec704b21f9243 15 | GNMI_CLI_SHA := sha256:6f1590c35e71c07406539d0e1e288e87e1e520ef58de25293441c3b9c81dffc0 16 | YANG_SHA := sha256:feb2dc322af113fc52f17b5735454abfbe017972c867e522ba53ea44e8386fd2 17 | SSHPASS_SHA := sha256:6e3d0d7564b259ef9612843d220cc390e52aab28b0ff9adaec800c72a051f41c 18 | -------------------------------------------------------------------------------- /util/docker/mvn/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2019-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Docker image to build the ONOS app. 16 | # Provides pre-poulated maven repo cache to allow offline builds. 17 | 18 | FROM maven:3.6.1-jdk-11-slim 19 | 20 | COPY . /mvn-src 21 | WORKDIR /mvn-src 22 | 23 | RUN mvn clean package && rm -rf ./* 24 | -------------------------------------------------------------------------------- /util/docker/stratum_bmv2/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2019-present Open Networking Foundation 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Docker image that extends opennetworking/mn-stratum with other dependencies 16 | # required by this tutorial. opennetworking/mn-stratum is the official image 17 | # from the Stratum project which contains stratum_bmv2 and the Mininet 18 | # libraries. We extend that with PTF, scapy, etc. 19 | 20 | ARG MN_STRATUM_SHA="sha256:1bba2e2c06460c73b0133ae22829937786217e5f20f8f80fcc3063dcf6707ebe" 21 | 22 | FROM bitnami/minideb:stretch as builder 23 | 24 | ENV BUILD_DEPS \ 25 | python-pip \ 26 | python-setuptools \ 27 | git 28 | RUN install_packages $BUILD_DEPS 29 | 30 | RUN mkdir -p /ouput 31 | 32 | ENV PIP_DEPS \ 33 | scapy==2.4.3 \ 34 | git+https://github.com/p4lang/ptf.git \ 35 | googleapis-common-protos==1.6.0 \ 36 | ipaddress 37 | RUN pip install --no-cache-dir --root /output $PIP_DEPS 38 | 39 | FROM opennetworking/mn-stratum:latest@$MN_STRATUM_SHA as runtime 40 | 41 | ENV RUNTIME_DEPS \ 42 | make 43 | RUN install_packages $RUNTIME_DEPS 44 | 45 | COPY --from=builder /output / 46 | 47 | ENV DOCKER_RUN true 48 | 49 | ENTRYPOINT [] 50 | -------------------------------------------------------------------------------- /util/gnmi-cli: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run --rm -it --network host bocon/gnmi-cli:latest $@ 3 | -------------------------------------------------------------------------------- /util/mn-cmd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z $1 ]; then 4 | echo "usage: $0 host cmd [args...]" 5 | exit 1 6 | fi 7 | 8 | docker exec -it mininet /mininet/host-cmd $@ 9 | -------------------------------------------------------------------------------- /util/mn-pcap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 4 | 5 | if [ -z $1 ]; then 6 | echo "usage: $0 host" 7 | exit 1 8 | fi 9 | 10 | iface=$1-eth0 11 | file=${iface}.pcap 12 | 13 | set -e 14 | 15 | echo "*** Starting tcpdump on ${iface}... Ctrl-c to stop capture" 16 | echo "*** Pcap file will be written in ngsdn-tutorial/tmp/${file}" 17 | docker exec -it mininet /mininet/host-cmd $1 tcpdump -i $1-eth0 -w /tmp/"${file}" 18 | 19 | if [ -x "$(command -v wireshark)" ]; then 20 | echo "*** Opening wireshark... Ctrl-c to quit" 21 | wireshark "${DIR}/../tmp/${file}" 22 | fi 23 | -------------------------------------------------------------------------------- /util/oc-pb-decoder: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker run --rm -i bocon/yang-tools:latest oc-pb-decoder 3 | -------------------------------------------------------------------------------- /util/onos-cmd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z $1 ]; then 4 | echo "usage: $0 cmd [args...]" 5 | exit 1 6 | fi 7 | 8 | # Use sshpass to skip the password prompt 9 | docker run -it --rm --network host ictu/sshpass \ 10 | -procks ssh -o "UserKnownHostsFile=/dev/null" \ 11 | -o "StrictHostKeyChecking=no" -o LogLevel=ERROR -p 8101 onos@localhost "$@" 12 | -------------------------------------------------------------------------------- /util/p4rt-sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2019 Barefoot Networks, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | """ 19 | P4Runtime shell docker wrapper 20 | From: https://github.com/p4lang/p4runtime-shell/blob/master/p4runtime-sh-docker 21 | """ 22 | 23 | import argparse 24 | from collections import namedtuple 25 | import logging 26 | import os.path 27 | import sys 28 | import tempfile 29 | import shutil 30 | import subprocess 31 | 32 | DOCKER_IMAGE = 'p4lang/p4runtime-sh' 33 | TMP_DIR = os.path.dirname(os.path.abspath(__file__)) + '/.pipe_cfg' 34 | 35 | 36 | def main(): 37 | FwdPipeConfig = namedtuple('FwdPipeConfig', ['p4info', 'bin']) 38 | 39 | def pipe_config(arg): 40 | try: 41 | paths = FwdPipeConfig(*[x for x in arg.split(',')]) 42 | if len(paths) != 2: 43 | raise argparse.ArgumentError 44 | return paths 45 | except Exception: 46 | raise argparse.ArgumentError( 47 | "Invalid pipeline config, expected ,") 48 | 49 | parser = argparse.ArgumentParser(description='P4Runtime shell docker wrapper', add_help=False) 50 | parser.add_argument('--grpc-addr', 51 | help='P4Runtime gRPC server address', 52 | metavar=':', 53 | type=str, action='store', default="localhost:50001") 54 | parser.add_argument('--config', 55 | help='If you want the shell to push a pipeline config to the server first', 56 | metavar=',', 57 | type=pipe_config, action='store', default=None) 58 | parser.add_argument('-v', '--verbose', help='Increase output verbosity', 59 | action='store_true') 60 | args, unknown_args = parser.parse_known_args() 61 | 62 | docker_args = [] 63 | new_args = [] 64 | 65 | if args.verbose: 66 | logging.basicConfig(level=logging.DEBUG) 67 | new_args.append('--verbose') 68 | 69 | if args.grpc_addr is not None: 70 | print("*** Connecting to P4Runtime server at {} ...".format(args.grpc_addr)) 71 | new_args.extend(["--grpc-addr", args.grpc_addr]) 72 | 73 | if args.config is not None: 74 | if not os.path.isfile(args.config.p4info): 75 | logging.critical("'{}' is not a valid file".format(args.config.p4info)) 76 | sys.exit(1) 77 | if not os.path.isfile(args.config.bin): 78 | logging.critical("'{}' is not a valid file".format(args.config.bin)) 79 | sys.exit(1) 80 | 81 | mount_path = "/fwd_pipe_config" 82 | fname_p4info = "p4info.pb.txt" 83 | fname_bin = "config.bin" 84 | 85 | os.mkdir(TMP_DIR) 86 | logging.debug( 87 | "Created temporary directory '{}', it will be mounted in the docker as '{}'".format( 88 | TMP_DIR, mount_path)) 89 | shutil.copy(args.config.p4info, os.path.join(TMP_DIR, fname_p4info)) 90 | shutil.copy(args.config.bin, os.path.join(TMP_DIR, fname_bin)) 91 | 92 | docker_args.extend(["-v", "{}:{}".format(TMP_DIR, mount_path)]) 93 | new_args.extend(["--config", "{},{}".format( 94 | os.path.join(mount_path, fname_p4info), os.path.join(mount_path, fname_bin))]) 95 | 96 | cmd = ["docker", "run", "-ti", "--network", "host"] 97 | cmd.extend(docker_args) 98 | cmd.append(DOCKER_IMAGE) 99 | cmd.extend(new_args) 100 | cmd.extend(unknown_args) 101 | logging.debug("Running cmd: {}".format(" ".join(cmd))) 102 | 103 | subprocess.run(cmd) 104 | 105 | if args.config is not None: 106 | logging.debug("Cleaning up...") 107 | try: 108 | shutil.rmtree(TMP_DIR) 109 | except Exception: 110 | logging.error("Error when removing temporary directory '{}'".format(TMP_DIR)) 111 | 112 | 113 | if __name__ == '__main__': 114 | main() 115 | -------------------------------------------------------------------------------- /util/vm/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .vagrant 3 | *.ova 4 | -------------------------------------------------------------------------------- /util/vm/README.md: -------------------------------------------------------------------------------- 1 | # Scripts to build the tutorial VM 2 | 3 | ## Requirements 4 | 5 | - [Vagrant](https://www.vagrantup.com/) (tested v2.2.5) 6 | - [VirtualBox](https://www.virtualbox.org/wiki/Downloads) (tested with v5.2.32) 7 | 8 | ## Steps to build 9 | 10 | If you want to provision and use the VM locally on your machine: 11 | 12 | cd util/vm 13 | vagrant up 14 | 15 | Otherwise, if you want to export the VM in `.ova` format for distribution to 16 | tutorial attendees: 17 | 18 | cd util/vm 19 | ./build-vm.sh 20 | 21 | This script will: 22 | 23 | 1. provision the VM using Vagrant; 24 | 2. reduce VM disk size; 25 | 3. generate a file named `ngsdn-tutorial.ova`. 26 | 27 | Use credentials `sdn`/`rocks` to log in the Ubuntu system. 28 | 29 | **Note on IntelliJ IDEA plugins:** plugins need to be installed manually. We 30 | recommend installing the following ones: 31 | 32 | * https://plugins.jetbrains.com/plugin/10620-p4-plugin 33 | * https://plugins.jetbrains.com/plugin/7322-python-community-edition -------------------------------------------------------------------------------- /util/vm/Vagrantfile: -------------------------------------------------------------------------------- 1 | REQUIRED_PLUGINS = %w( vagrant-vbguest vagrant-reload vagrant-disksize ) 2 | 3 | Vagrant.configure(2) do |config| 4 | 5 | # Install plugins if missing... 6 | _retry = false 7 | REQUIRED_PLUGINS.each do |plugin| 8 | unless Vagrant.has_plugin? plugin 9 | system "vagrant plugin install #{plugin}" 10 | _retry = true 11 | end 12 | end 13 | 14 | if (_retry) 15 | exec "vagrant " + ARGV.join(' ') 16 | end 17 | 18 | # Common config. 19 | config.vm.box = "lasp/ubuntu16.04-desktop" 20 | config.vbguest.auto_update = true 21 | config.disksize.size = '50GB' 22 | config.vm.synced_folder ".", "/vagrant", disabled: false, type: "virtualbox" 23 | config.vm.network "private_network", :type => 'dhcp', :adapter => 2 24 | 25 | config.vm.define "default" do |d| 26 | d.vm.hostname = "tutorial-vm" 27 | d.vm.provider "virtualbox" do |vb| 28 | vb.name = "ONF NG-SDN Tutorial " + Time.now.strftime("(%Y-%m-%d)") 29 | vb.gui = true 30 | vb.cpus = 8 31 | vb.memory = 8192 32 | vb.customize ['modifyvm', :id, '--clipboard', 'bidirectional'] 33 | vb.customize ["modifyvm", :id, "--accelerate3d", "on"] 34 | vb.customize ["modifyvm", :id, "--graphicscontroller", "vboxvga"] 35 | vb.customize ["modifyvm", :id, "--vram", "128"] 36 | end 37 | d.vm.provision "shell", path: "root-bootstrap.sh" 38 | d.vm.provision "shell", inline: "su sdn '/vagrant/user-bootstrap.sh'" 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /util/vm/build-vm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -xe 4 | 5 | function wait_vm_shutdown { 6 | set +x 7 | while vboxmanage showvminfo $1 | grep -c "running (since"; do 8 | echo "Waiting for VM to shutdown..." 9 | sleep 1 10 | done 11 | sleep 2 12 | set -x 13 | } 14 | 15 | # Provision 16 | vagrant up 17 | 18 | # Cleanup 19 | VB_UUID=$(cat .vagrant/machines/default/virtualbox/id) 20 | vagrant ssh -c 'bash /vagrant/cleanup.sh' 21 | sleep 5 22 | vboxmanage controlvm "${VB_UUID}" acpipowerbutton 23 | wait_vm_shutdown "${VB_UUID}" 24 | # Remove vagrant shared folder 25 | vboxmanage sharedfolder remove "${VB_UUID}" -name "vagrant" 26 | 27 | # Export 28 | rm -f ngsdn-tutorial.ova 29 | vboxmanage export "${VB_UUID}" -o ngsdn-tutorial.ova 30 | -------------------------------------------------------------------------------- /util/vm/cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | sudo apt-get clean 5 | sudo apt-get -y autoremove 6 | 7 | sudo rm -rf /tmp/* 8 | 9 | history -c 10 | rm -f ~/.bash_history 11 | 12 | # Zerofill virtual hd to save space when exporting 13 | time sudo dd if=/dev/zero of=/tmp/zero bs=1M || true 14 | sync ; sleep 1 ; sync ; sudo rm -f /tmp/zero 15 | 16 | # Delete vagrant user 17 | sudo userdel -r -f vagrant 18 | -------------------------------------------------------------------------------- /util/vm/root-bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -xe 4 | 5 | # Create user sdn 6 | useradd -m -d /home/sdn -s /bin/bash sdn 7 | usermod -aG sudo sdn 8 | usermod -aG vboxsf sdn 9 | echo "sdn:rocks" | chpasswd 10 | echo "sdn ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/99_sdn 11 | chmod 440 /etc/sudoers.d/99_sdn 12 | update-locale LC_ALL="en_US.UTF-8" 13 | 14 | apt-get update 15 | 16 | apt-get install -y --no-install-recommends apt-transport-https ca-certificates 17 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - 18 | add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" 19 | apt-get update 20 | 21 | # Required packages 22 | DEBIAN_FRONTEND=noninteractive apt-get -y --no-install-recommends install \ 23 | avahi-daemon \ 24 | git \ 25 | bash-completion \ 26 | htop \ 27 | python \ 28 | zip unzip \ 29 | make \ 30 | wget \ 31 | curl \ 32 | vim nano emacs \ 33 | docker-ce 34 | 35 | # Enable Docker at startup 36 | systemctl start docker 37 | systemctl enable docker 38 | # Add sdn user to docker group 39 | usermod -a -G docker sdn 40 | 41 | # Install pip 42 | curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py 43 | python get-pip.py --force-reinstall 44 | rm -f get-pip.py 45 | 46 | # Bash autocompletion 47 | echo "source /etc/profile.d/bash_completion.sh" >> ~/.bashrc 48 | 49 | # Fix SSH server config 50 | tee -a /etc/ssh/sshd_config <