blob: 4912847fd523d3ee37937bb37bc92cd6657e826b [file] [log] [blame]
/*
* Copyright 2017-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This program describes a pipeline implementing a very simple
* tunneling protocol called MyTunnel. The pipeline defines also table called
* t_l2_fwd that provides basic L2 forwarding capabilities and actions to
* send packets to the controller. This table is needed to provide
* compatibility with existing ONOS applications such as Proxy-ARP, LLDP Link
* Discovery and Reactive Forwarding.
*/
#include <core.p4>
#include <v1model.p4>
#define MAX_PORTS 255
const bit<16> ETH_TYPE_MYTUNNEL = 0x1212;
const bit<16> ETH_TYPE_IPV4 = 0x800;
typedef bit<9> port_t;
const port_t CPU_PORT = 255;
//------------------------------------------------------------------------------
// HEADERS
//------------------------------------------------------------------------------
header ethernet_t {
bit<48> dst_addr;
bit<48> src_addr;
bit<16> ether_type;
}
header my_tunnel_t {
bit<16> proto_id;
bit<32> tun_id;
}
header ipv4_t {
bit<4> version;
bit<4> ihl;
bit<8> diffserv;
bit<16> len;
bit<16> identification;
bit<3> flags;
bit<13> frag_offset;
bit<8> ttl;
bit<8> protocol;
bit<16> hdr_checksum;
bit<32> src_addr;
bit<32> dst_addr;
}
// Packet-in header. Prepended to packets sent to the controller and used to
// carry the original ingress port where the packet was received.
@controller_header("packet_in")
header packet_in_header_t {
bit<9> ingress_port;
}
// Packet-out header. Prepended to packets received by the controller and used
// to tell the switch on which port this packet should be forwarded.
@controller_header("packet_out")
header packet_out_header_t {
bit<9> egress_port;
}
// For convenience we collect all headers under the same struct.
struct headers_t {
ethernet_t ethernet;
my_tunnel_t my_tunnel;
ipv4_t ipv4;
packet_out_header_t packet_out;
packet_in_header_t packet_in;
}
// Metadata can be used to carry information from one table to another.
struct metadata_t {
// Empty. We don't use it in this program.
}
//------------------------------------------------------------------------------
// PARSER
//------------------------------------------------------------------------------
parser c_parser(packet_in packet,
out headers_t hdr,
inout metadata_t meta,
inout standard_metadata_t standard_metadata) {
// A P4 parser is described as a state machine, with initial state "start"
// and final one "accept". Each intermediate state can specify the next
// state by using a select statement over the header fields extracted.
state start {
transition select(standard_metadata.ingress_port) {
CPU_PORT: parse_packet_out;
default: parse_ethernet;
}
}
state parse_packet_out {
packet.extract(hdr.packet_out);
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.ether_type) {
ETH_TYPE_MYTUNNEL: parse_my_tunnel;
ETH_TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_my_tunnel {
packet.extract(hdr.my_tunnel);
transition select(hdr.my_tunnel.proto_id) {
ETH_TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition accept;
}
}
//------------------------------------------------------------------------------
// INGRESS PIPELINE
//------------------------------------------------------------------------------
control c_ingress(inout headers_t hdr,
inout metadata_t meta,
inout standard_metadata_t standard_metadata) {
// We use these counters to count packets/bytes received/sent on each port.
// For each counter we instantiate a number of cells equal to MAX_PORTS.
counter(MAX_PORTS, CounterType.packets_and_bytes) tx_port_counter;
counter(MAX_PORTS, CounterType.packets_and_bytes) rx_port_counter;
action send_to_cpu() {
standard_metadata.egress_spec = CPU_PORT;
// Packets sent to the controller needs to be prepended with the
// packet-in header. By setting it valid we make sure it will be
// deparsed on the wire (see c_deparser).
hdr.packet_in.setValid();
hdr.packet_in.ingress_port = standard_metadata.ingress_port;
}
action set_out_port(port_t port) {
// Specifies the output port for this packet by setting the
// corresponding metadata.
standard_metadata.egress_spec = port;
}
action _drop() {
mark_to_drop();
}
action my_tunnel_ingress(bit<32> tun_id) {
hdr.my_tunnel.setValid();
hdr.my_tunnel.tun_id = tun_id;
hdr.my_tunnel.proto_id = hdr.ethernet.ether_type;
hdr.ethernet.ether_type = ETH_TYPE_MYTUNNEL;
}
action my_tunnel_egress(bit<9> port) {
standard_metadata.egress_spec = port;
hdr.ethernet.ether_type = hdr.my_tunnel.proto_id;
hdr.my_tunnel.setInvalid();
}
// Table counter used to count packets and bytes matched by each entry of
// t_l2_fwd table.
direct_counter(CounterType.packets_and_bytes) l2_fwd_counter;
table t_l2_fwd {
key = {
standard_metadata.ingress_port : ternary;
hdr.ethernet.dst_addr : ternary;
hdr.ethernet.src_addr : ternary;
hdr.ethernet.ether_type : ternary;
}
actions = {
set_out_port();
send_to_cpu();
_drop();
NoAction;
}
default_action = NoAction();
counters = l2_fwd_counter;
}
table t_tunnel_ingress {
key = {
hdr.ipv4.dst_addr: lpm;
}
actions = {
my_tunnel_ingress;
_drop();
}
default_action = _drop();
}
table t_tunnel_fwd {
key = {
hdr.my_tunnel.tun_id: exact;
}
actions = {
set_out_port;
my_tunnel_egress;
_drop();
}
default_action = _drop();
}
// Defines the processing applied by this control block. You can see this as
// the main function applied to every packet received by the switch.
apply {
if (standard_metadata.ingress_port == CPU_PORT) {
// Packet received from CPU_PORT, this is a packet-out sent by the
// controller. Skip table processing, set the egress port as
// requested by the controller (packet_out header) and remove the
// packet_out header.
standard_metadata.egress_spec = hdr.packet_out.egress_port;
hdr.packet_out.setInvalid();
} else {
// Packet received from data plane port.
// Applies table t_l2_fwd to the packet.
if (t_l2_fwd.apply().hit) {
// Packet hit an entry in t_l2_fwd table. A forwarding action
// has already been taken. No need to apply other tables, exit
// this control block.
return;
}
if (hdr.ipv4.isValid() && !hdr.my_tunnel.isValid()) {
// Process only non-tunneled IPv4 packets.
t_tunnel_ingress.apply();
}
if (hdr.my_tunnel.isValid()) {
// Process all tunneled packets.
t_tunnel_fwd.apply();
}
}
// Update port counters at index = ingress or egress port.
if (standard_metadata.egress_spec < MAX_PORTS) {
tx_port_counter.count((bit<32>) standard_metadata.egress_spec);
}
if (standard_metadata.ingress_port < MAX_PORTS) {
rx_port_counter.count((bit<32>) standard_metadata.ingress_port);
}
}
}
//------------------------------------------------------------------------------
// EGRESS PIPELINE
//------------------------------------------------------------------------------
control c_egress(inout headers_t hdr,
inout metadata_t meta,
inout standard_metadata_t standard_metadata) {
apply {
// Nothing to do on the egress pipeline.
}
}
//------------------------------------------------------------------------------
// CHECKSUM HANDLING
//------------------------------------------------------------------------------
control c_verify_checksum(inout headers_t hdr, inout metadata_t meta) {
apply {
// Nothing to do here, we assume checksum is always correct.
}
}
control c_compute_checksum(inout headers_t hdr, inout metadata_t meta) {
apply {
// No need to compute checksum as we do not modify packet headers.
}
}
//------------------------------------------------------------------------------
// DEPARSER
//------------------------------------------------------------------------------
control c_deparser(packet_out packet, in headers_t hdr) {
apply {
// Emit headers on the wire in the following order.
// Only valid headers are emitted.
packet.emit(hdr.packet_in);
packet.emit(hdr.ethernet);
packet.emit(hdr.my_tunnel);
packet.emit(hdr.ipv4);
}
}
//------------------------------------------------------------------------------
// SWITCH INSTANTIATION
//------------------------------------------------------------------------------
V1Switch(c_parser(),
c_verify_checksum(),
c_ingress(),
c_egress(),
c_compute_checksum(),
c_deparser()) main;