Carmelo Cascone | 700648c | 2018-04-11 12:02:16 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2017-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 | |
Carmelo Cascone | 9bc6a14 | 2018-04-16 17:35:15 -0700 | [diff] [blame] | 17 | /* |
| 18 | * This program describes a pipeline implementing a very simple |
| 19 | * tunneling protocol called MyTunnel. The pipeline defines also table called |
| 20 | * t_l2_fwd that provides basic L2 forwarding capabilities and actions to |
| 21 | * send packets to the controller. This table is needed to provide |
| 22 | * compatibility with existing ONOS applications such as Proxy-ARP, LLDP Link |
| 23 | * Discovery and Reactive Forwarding. |
| 24 | */ |
| 25 | |
Carmelo Cascone | 700648c | 2018-04-11 12:02:16 -0700 | [diff] [blame] | 26 | #include <core.p4> |
| 27 | #include <v1model.p4> |
| 28 | |
| 29 | #define MAX_PORTS 255 |
| 30 | |
| 31 | const bit<16> ETH_TYPE_MYTUNNEL = 0x1212; |
| 32 | const bit<16> ETH_TYPE_IPV4 = 0x800; |
| 33 | |
| 34 | typedef bit<9> port_t; |
| 35 | const port_t CPU_PORT = 255; |
| 36 | |
| 37 | //------------------------------------------------------------------------------ |
| 38 | // HEADERS |
| 39 | //------------------------------------------------------------------------------ |
| 40 | |
| 41 | header ethernet_t { |
| 42 | bit<48> dst_addr; |
| 43 | bit<48> src_addr; |
| 44 | bit<16> ether_type; |
| 45 | } |
| 46 | |
| 47 | header my_tunnel_t { |
| 48 | bit<16> proto_id; |
| 49 | bit<32> tun_id; |
| 50 | } |
| 51 | |
| 52 | header ipv4_t { |
| 53 | bit<4> version; |
| 54 | bit<4> ihl; |
| 55 | bit<8> diffserv; |
| 56 | bit<16> len; |
| 57 | bit<16> identification; |
| 58 | bit<3> flags; |
| 59 | bit<13> frag_offset; |
| 60 | bit<8> ttl; |
| 61 | bit<8> protocol; |
| 62 | bit<16> hdr_checksum; |
| 63 | bit<32> src_addr; |
| 64 | bit<32> dst_addr; |
| 65 | } |
| 66 | |
| 67 | // Packet-in header. Prepended to packets sent to the controller and used to |
| 68 | // carry the original ingress port where the packet was received. |
| 69 | @controller_header("packet_in") |
| 70 | header packet_in_header_t { |
| 71 | bit<9> ingress_port; |
| 72 | } |
| 73 | |
| 74 | // Packet-out header. Prepended to packets received by the controller and used |
| 75 | // to tell the switch on which port this packet should be forwarded. |
| 76 | @controller_header("packet_out") |
| 77 | header packet_out_header_t { |
| 78 | bit<9> egress_port; |
| 79 | } |
| 80 | |
| 81 | // For convenience we collect all headers under the same struct. |
| 82 | struct headers_t { |
| 83 | ethernet_t ethernet; |
| 84 | my_tunnel_t my_tunnel; |
| 85 | ipv4_t ipv4; |
| 86 | packet_out_header_t packet_out; |
| 87 | packet_in_header_t packet_in; |
| 88 | } |
| 89 | |
| 90 | // Metadata can be used to carry information from one table to another. |
| 91 | struct metadata_t { |
| 92 | // Empty. We don't use it in this program. |
| 93 | } |
| 94 | |
| 95 | //------------------------------------------------------------------------------ |
| 96 | // PARSER |
| 97 | //------------------------------------------------------------------------------ |
| 98 | |
| 99 | parser c_parser(packet_in packet, |
| 100 | out headers_t hdr, |
| 101 | inout metadata_t meta, |
| 102 | inout standard_metadata_t standard_metadata) { |
| 103 | |
Carmelo Cascone | 9bc6a14 | 2018-04-16 17:35:15 -0700 | [diff] [blame] | 104 | // A P4 parser is described as a state machine, with initial state "start" |
| 105 | // and final one "accept". Each intermediate state can specify the next |
| 106 | // state by using a select statement over the header fields extracted. |
Carmelo Cascone | 700648c | 2018-04-11 12:02:16 -0700 | [diff] [blame] | 107 | state start { |
| 108 | transition select(standard_metadata.ingress_port) { |
| 109 | CPU_PORT: parse_packet_out; |
| 110 | default: parse_ethernet; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | state parse_packet_out { |
| 115 | packet.extract(hdr.packet_out); |
| 116 | transition parse_ethernet; |
| 117 | } |
| 118 | |
| 119 | state parse_ethernet { |
| 120 | packet.extract(hdr.ethernet); |
| 121 | transition select(hdr.ethernet.ether_type) { |
| 122 | ETH_TYPE_MYTUNNEL: parse_my_tunnel; |
| 123 | ETH_TYPE_IPV4: parse_ipv4; |
| 124 | default: accept; |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | state parse_my_tunnel { |
| 129 | packet.extract(hdr.my_tunnel); |
| 130 | transition select(hdr.my_tunnel.proto_id) { |
| 131 | ETH_TYPE_IPV4: parse_ipv4; |
| 132 | default: accept; |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | state parse_ipv4 { |
| 137 | packet.extract(hdr.ipv4); |
| 138 | transition accept; |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | //------------------------------------------------------------------------------ |
| 143 | // INGRESS PIPELINE |
| 144 | //------------------------------------------------------------------------------ |
| 145 | |
| 146 | control c_ingress(inout headers_t hdr, |
| 147 | inout metadata_t meta, |
| 148 | inout standard_metadata_t standard_metadata) { |
| 149 | |
| 150 | // We use these counters to count packets/bytes received/sent on each port. |
| 151 | // For each counter we instantiate a number of cells equal to MAX_PORTS. |
| 152 | counter(MAX_PORTS, CounterType.packets_and_bytes) tx_port_counter; |
| 153 | counter(MAX_PORTS, CounterType.packets_and_bytes) rx_port_counter; |
| 154 | |
| 155 | action send_to_cpu() { |
| 156 | standard_metadata.egress_spec = CPU_PORT; |
| 157 | // Packets sent to the controller needs to be prepended with the |
| 158 | // packet-in header. By setting it valid we make sure it will be |
| 159 | // deparsed on the wire (see c_deparser). |
| 160 | hdr.packet_in.setValid(); |
| 161 | hdr.packet_in.ingress_port = standard_metadata.ingress_port; |
| 162 | } |
| 163 | |
| 164 | action set_out_port(port_t port) { |
Carmelo Cascone | 9bc6a14 | 2018-04-16 17:35:15 -0700 | [diff] [blame] | 165 | // Specifies the output port for this packet by setting the |
| 166 | // corresponding metadata. |
Carmelo Cascone | 700648c | 2018-04-11 12:02:16 -0700 | [diff] [blame] | 167 | standard_metadata.egress_spec = port; |
| 168 | } |
| 169 | |
| 170 | action _drop() { |
| 171 | mark_to_drop(); |
| 172 | } |
| 173 | |
| 174 | action my_tunnel_ingress(bit<32> tun_id) { |
| 175 | hdr.my_tunnel.setValid(); |
| 176 | hdr.my_tunnel.tun_id = tun_id; |
| 177 | hdr.my_tunnel.proto_id = hdr.ethernet.ether_type; |
| 178 | hdr.ethernet.ether_type = ETH_TYPE_MYTUNNEL; |
| 179 | } |
| 180 | |
| 181 | action my_tunnel_egress(bit<9> port) { |
| 182 | standard_metadata.egress_spec = port; |
| 183 | hdr.ethernet.ether_type = hdr.my_tunnel.proto_id; |
| 184 | hdr.my_tunnel.setInvalid(); |
| 185 | } |
| 186 | |
Carmelo Cascone | 9bc6a14 | 2018-04-16 17:35:15 -0700 | [diff] [blame] | 187 | // Table counter used to count packets and bytes matched by each entry of |
| 188 | // t_l2_fwd table. |
Carmelo Cascone | 700648c | 2018-04-11 12:02:16 -0700 | [diff] [blame] | 189 | direct_counter(CounterType.packets_and_bytes) l2_fwd_counter; |
| 190 | |
| 191 | table t_l2_fwd { |
| 192 | key = { |
| 193 | standard_metadata.ingress_port : ternary; |
| 194 | hdr.ethernet.dst_addr : ternary; |
| 195 | hdr.ethernet.src_addr : ternary; |
| 196 | hdr.ethernet.ether_type : ternary; |
| 197 | } |
| 198 | actions = { |
| 199 | set_out_port(); |
| 200 | send_to_cpu(); |
| 201 | _drop(); |
| 202 | NoAction; |
| 203 | } |
| 204 | default_action = NoAction(); |
| 205 | counters = l2_fwd_counter; |
| 206 | } |
| 207 | |
| 208 | table t_tunnel_ingress { |
| 209 | key = { |
| 210 | hdr.ipv4.dst_addr: lpm; |
| 211 | } |
| 212 | actions = { |
| 213 | my_tunnel_ingress; |
| 214 | _drop(); |
| 215 | } |
| 216 | default_action = _drop(); |
| 217 | } |
| 218 | |
| 219 | table t_tunnel_fwd { |
| 220 | key = { |
| 221 | hdr.my_tunnel.tun_id: exact; |
| 222 | } |
| 223 | actions = { |
| 224 | set_out_port; |
| 225 | my_tunnel_egress; |
| 226 | _drop(); |
| 227 | } |
| 228 | default_action = _drop(); |
| 229 | } |
| 230 | |
Carmelo Cascone | 9bc6a14 | 2018-04-16 17:35:15 -0700 | [diff] [blame] | 231 | // Defines the processing applied by this control block. You can see this as |
| 232 | // the main function applied to every packet received by the switch. |
Carmelo Cascone | 700648c | 2018-04-11 12:02:16 -0700 | [diff] [blame] | 233 | apply { |
| 234 | if (standard_metadata.ingress_port == CPU_PORT) { |
| 235 | // Packet received from CPU_PORT, this is a packet-out sent by the |
| 236 | // controller. Skip table processing, set the egress port as |
| 237 | // requested by the controller (packet_out header) and remove the |
| 238 | // packet_out header. |
| 239 | standard_metadata.egress_spec = hdr.packet_out.egress_port; |
| 240 | hdr.packet_out.setInvalid(); |
| 241 | } else { |
| 242 | // Packet received from data plane port. |
Carmelo Cascone | 9bc6a14 | 2018-04-16 17:35:15 -0700 | [diff] [blame] | 243 | // Applies table t_l2_fwd to the packet. |
Carmelo Cascone | 700648c | 2018-04-11 12:02:16 -0700 | [diff] [blame] | 244 | if (t_l2_fwd.apply().hit) { |
| 245 | // Packet hit an entry in t_l2_fwd table. A forwarding action |
| 246 | // has already been taken. No need to apply other tables, exit |
| 247 | // this control block. |
| 248 | return; |
| 249 | } |
| 250 | |
| 251 | if (hdr.ipv4.isValid() && !hdr.my_tunnel.isValid()) { |
| 252 | // Process only non-tunneled IPv4 packets. |
| 253 | t_tunnel_ingress.apply(); |
| 254 | } |
| 255 | |
| 256 | if (hdr.my_tunnel.isValid()) { |
| 257 | // Process all tunneled packets. |
| 258 | t_tunnel_fwd.apply(); |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | // Update port counters at index = ingress or egress port. |
| 263 | if (standard_metadata.egress_spec < MAX_PORTS) { |
| 264 | tx_port_counter.count((bit<32>) standard_metadata.egress_spec); |
| 265 | } |
| 266 | if (standard_metadata.ingress_port < MAX_PORTS) { |
| 267 | rx_port_counter.count((bit<32>) standard_metadata.ingress_port); |
| 268 | } |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | //------------------------------------------------------------------------------ |
| 273 | // EGRESS PIPELINE |
| 274 | //------------------------------------------------------------------------------ |
| 275 | |
| 276 | control c_egress(inout headers_t hdr, |
| 277 | inout metadata_t meta, |
| 278 | inout standard_metadata_t standard_metadata) { |
| 279 | apply { |
| 280 | // Nothing to do on the egress pipeline. |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | //------------------------------------------------------------------------------ |
| 285 | // CHECKSUM HANDLING |
| 286 | //------------------------------------------------------------------------------ |
| 287 | |
| 288 | control c_verify_checksum(inout headers_t hdr, inout metadata_t meta) { |
| 289 | apply { |
| 290 | // Nothing to do here, we assume checksum is always correct. |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | control c_compute_checksum(inout headers_t hdr, inout metadata_t meta) { |
| 295 | apply { |
| 296 | // No need to compute checksum as we do not modify packet headers. |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | //------------------------------------------------------------------------------ |
| 301 | // DEPARSER |
| 302 | //------------------------------------------------------------------------------ |
| 303 | |
| 304 | control c_deparser(packet_out packet, in headers_t hdr) { |
| 305 | apply { |
| 306 | // Emit headers on the wire in the following order. |
| 307 | // Only valid headers are emitted. |
| 308 | packet.emit(hdr.packet_in); |
| 309 | packet.emit(hdr.ethernet); |
| 310 | packet.emit(hdr.my_tunnel); |
| 311 | packet.emit(hdr.ipv4); |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | //------------------------------------------------------------------------------ |
| 316 | // SWITCH INSTANTIATION |
| 317 | //------------------------------------------------------------------------------ |
| 318 | |
| 319 | V1Switch(c_parser(), |
| 320 | c_verify_checksum(), |
| 321 | c_ingress(), |
| 322 | c_egress(), |
| 323 | c_compute_checksum(), |
| 324 | c_deparser()) main; |