blob: 0aa78c057dbfcdc776f0dc75ebefddbbb6fb3c50 [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.
*/
#ifndef __SPGW__
#define __SPGW__
#define MAX_PDR_COUNTERS 1024
#define DEFAULT_PDR_CTR_ID 0
#define DEFAULT_FAR_ID 0
control spgw_normalizer(
in bool is_gtpu_encapped,
out ipv4_t gtpu_ipv4,
out udp_t gtpu_udp,
inout ipv4_t ipv4,
inout udp_t udp,
in ipv4_t inner_ipv4,
in udp_t inner_udp
) {
apply {
if (! is_gtpu_encapped) return;
gtpu_ipv4 = ipv4;
ipv4 = inner_ipv4;
gtpu_udp = udp;
if (inner_udp.isValid()) {
udp = inner_udp;
} else {
udp.setInvalid();
}
}
}
control spgw_ingress(
inout ipv4_t gtpu_ipv4,
inout udp_t gtpu_udp,
inout gtpu_t gtpu,
inout ipv4_t ipv4,
inout udp_t udp,
inout fabric_metadata_t fabric_meta,
inout standard_metadata_t standard_metadata
) {
counter(MAX_PDR_COUNTERS, CounterType.packets_and_bytes) pdr_counter;
@hidden
action gtpu_decap() {
// grab information from the tunnel that we'll need later
fabric_meta.spgw.teid = gtpu.teid;
fabric_meta.spgw.tunnel_dst_addr = gtpu_ipv4.dst_addr;
// update metadata src and dst addresses with the inner packet
fabric_meta.ipv4_src_addr = ipv4.src_addr;
fabric_meta.ipv4_dst_addr = ipv4.dst_addr;
// decap
gtpu_ipv4.setInvalid();
gtpu_udp.setInvalid();
gtpu.setInvalid();
}
table downlink_filter_table {
key = {
// UE addr pool for downlink
ipv4.dst_addr : lpm @name("ipv4_prefix");
}
actions = {
nop();
}
const default_action = nop();
}
table uplink_filter_table {
key = {
// IP addresses of the S1U interfaces of this SPGW-U instance (when uplink)
gtpu_ipv4.dst_addr : exact @name("gtp_ipv4_dst");
}
actions = {
nop();
}
const default_action = nop();
}
action set_pdr_attributes(ctr_id_t ctr_id,
far_id_t far_id) {
fabric_meta.spgw.pdr_hit = _TRUE;
fabric_meta.spgw.ctr_id = ctr_id;
fabric_meta.spgw.far_id = far_id;
}
// These two tables scale well and cover the average case PDR
table downlink_pdr_lookup {
key = {
ipv4.dst_addr : exact @name("ue_addr");
}
actions = {
set_pdr_attributes;
}
}
table uplink_pdr_lookup {
key = {
// tunnel_dst_addr will be static for Q2 target. Can remove if need more scaling
fabric_meta.spgw.tunnel_dst_addr : exact @name("tunnel_ipv4_dst");
fabric_meta.spgw.teid : exact @name("teid");
ipv4.src_addr : exact @name("ue_addr");
}
actions = {
set_pdr_attributes;
}
}
// This table scales poorly and covers uncommon PDRs
table flexible_pdr_lookup {
key = {
// Direction. Eventually change to interface
fabric_meta.spgw.direction : ternary @name("spgw_direction");
// F-TEID
fabric_meta.spgw.tunnel_dst_addr : ternary @name("tunnel_ipv4_dst");
fabric_meta.spgw.teid : ternary @name("teid");
// SDF (5-tuple)
ipv4.src_addr : ternary @name("ipv4_src");
ipv4.dst_addr : ternary @name("ipv4_dst");
ipv4.protocol : ternary @name("ip_proto");
fabric_meta.l4_sport : ternary @name("l4_sport");
fabric_meta.l4_dport : ternary @name("l4_dport");
}
actions = {
set_pdr_attributes;
}
const default_action = set_pdr_attributes(DEFAULT_PDR_CTR_ID, DEFAULT_FAR_ID);
}
action load_normal_far_attributes(bit<1> drop,
bit<1> notify_cp) {
// general far attributes
fabric_meta.spgw.far_dropped = (_BOOL)drop;
fabric_meta.spgw.notify_cp = (_BOOL)notify_cp;
}
action load_tunnel_far_attributes(bit<1> drop,
bit<1> notify_cp,
ipv4_addr_t tunnel_src_addr,
ipv4_addr_t tunnel_dst_addr,
teid_t teid) {
// general far attributes
fabric_meta.spgw.far_dropped = (_BOOL)drop;
fabric_meta.spgw.notify_cp = (_BOOL)notify_cp;
// GTP tunnel attributes
fabric_meta.spgw.outer_header_creation = _TRUE;
fabric_meta.spgw.teid = teid;
fabric_meta.spgw.tunnel_src_addr = tunnel_src_addr;
fabric_meta.spgw.tunnel_dst_addr = tunnel_dst_addr;
// update metadata IP addresses for correct routing/hashing
fabric_meta.ipv4_src_addr = tunnel_src_addr;
fabric_meta.ipv4_dst_addr = tunnel_dst_addr;
}
table far_lookup {
key = {
fabric_meta.spgw.far_id : exact @name("far_id");
}
actions = {
load_normal_far_attributes;
load_tunnel_far_attributes;
}
// default is drop and don't notify CP
const default_action = load_normal_far_attributes(1w1, 1w0);
}
apply {
if (gtpu.isValid()) {
// If here, pkt has outer IP dst on
// S1U_SGW_PREFIX/S1U_SGW_PREFIX_LEN subnet.
// TODO: check also that gtpu.msgtype == GTP_GPDU
if (!uplink_filter_table.apply().hit) {
// Should this be changed to a forwarding/next skip instead of a drop?
mark_to_drop(standard_metadata);
}
fabric_meta.spgw.direction = SPGW_DIR_UPLINK;
gtpu_decap();
} else if (downlink_filter_table.apply().hit) {
fabric_meta.spgw.direction = SPGW_DIR_DOWNLINK;
} else {
fabric_meta.spgw.direction = SPGW_DIR_UNKNOWN;
// No SPGW processing needed.
return;
}
// Try the efficient PDR tables first (This PDR partitioning only works
// if the PDRs do not overlap. Will need fixing later.)
if (fabric_meta.spgw.direction == SPGW_DIR_UPLINK) {
uplink_pdr_lookup.apply();
} else if (fabric_meta.spgw.direction == SPGW_DIR_DOWNLINK) {
downlink_pdr_lookup.apply();
} else { // SPGW_DIR_UNKNOWN
return;
}
// If those fail to find a match, use the wildcard tables
if (fabric_meta.spgw.pdr_hit == _FALSE) {
flexible_pdr_lookup.apply();
}
pdr_counter.count(fabric_meta.spgw.ctr_id);
// Load FAR info
far_lookup.apply();
if (fabric_meta.spgw.notify_cp == _TRUE) {
// TODO: cpu clone session here
}
if (fabric_meta.spgw.far_dropped == _TRUE) {
// Do dropping in the same way as fabric's filtering.p4, so we can traverse
// the ACL table, which is good for cases like DHCP.
fabric_meta.skip_forwarding = _TRUE;
fabric_meta.skip_next = _TRUE;
}
// Nothing to be done immediately for forwarding or encapsulation.
// Forwarding is done by other parts of fabric.p4, and
// encapsulation is done in the egress
// Needed for correct GTPU encapsulation in egress
fabric_meta.spgw.ipv4_len = ipv4.total_len;
}
}
control spgw_egress(
in ipv4_t ipv4,
inout ipv4_t gtpu_ipv4,
inout udp_t gtpu_udp,
inout gtpu_t gtpu,
in fabric_metadata_t fabric_meta,
in standard_metadata_t std_meta
) {
counter(MAX_PDR_COUNTERS, CounterType.packets_and_bytes) pdr_counter;
@hidden
action gtpu_encap() {
gtpu_ipv4.setValid();
gtpu_ipv4.version = IP_VERSION_4;
gtpu_ipv4.ihl = IPV4_MIN_IHL;
gtpu_ipv4.dscp = 0;
gtpu_ipv4.ecn = 0;
gtpu_ipv4.total_len = ipv4.total_len
+ (IPV4_HDR_SIZE + UDP_HDR_SIZE + GTP_HDR_SIZE);
gtpu_ipv4.identification = 0x1513; /* From NGIC. TODO: Needs to be dynamic */
gtpu_ipv4.flags = 0;
gtpu_ipv4.frag_offset = 0;
gtpu_ipv4.ttl = DEFAULT_IPV4_TTL;
gtpu_ipv4.protocol = PROTO_UDP;
gtpu_ipv4.src_addr = fabric_meta.spgw.tunnel_src_addr;
gtpu_ipv4.dst_addr = fabric_meta.spgw.tunnel_dst_addr;
gtpu_ipv4.hdr_checksum = 0; // Updated later
gtpu_udp.setValid();
gtpu_udp.sport = UDP_PORT_GTPU; // TODO: make this dynamic per 3GPP specs
gtpu_udp.dport = UDP_PORT_GTPU;
gtpu_udp.len = fabric_meta.spgw.ipv4_len
+ (UDP_HDR_SIZE + GTP_HDR_SIZE);
gtpu_udp.checksum = 0; // Updated later, if WITH_SPGW_UDP_CSUM_UPDATE
gtpu.setValid();
gtpu.version = GTPU_VERSION;
gtpu.pt = GTP_PROTOCOL_TYPE_GTP;
gtpu.spare = 0;
gtpu.ex_flag = 0;
gtpu.seq_flag = 0;
gtpu.npdu_flag = 0;
gtpu.msgtype = GTP_GPDU;
gtpu.msglen = fabric_meta.spgw.ipv4_len;
gtpu.teid = fabric_meta.spgw.teid;
}
apply {
pdr_counter.count(fabric_meta.spgw.ctr_id);
if (fabric_meta.spgw.outer_header_creation == _TRUE) {
gtpu_encap();
}
}
}
control update_gtpu_checksum(
inout ipv4_t gtpu_ipv4,
inout udp_t gtpu_udp,
in gtpu_t gtpu,
in ipv4_t ipv4,
in udp_t udp
) {
apply {
// Compute outer IPv4 checksum.
update_checksum(gtpu_ipv4.isValid(),
{
gtpu_ipv4.version,
gtpu_ipv4.ihl,
gtpu_ipv4.dscp,
gtpu_ipv4.ecn,
gtpu_ipv4.total_len,
gtpu_ipv4.identification,
gtpu_ipv4.flags,
gtpu_ipv4.frag_offset,
gtpu_ipv4.ttl,
gtpu_ipv4.protocol,
gtpu_ipv4.src_addr,
gtpu_ipv4.dst_addr
},
gtpu_ipv4.hdr_checksum,
HashAlgorithm.csum16
);
#ifdef WITH_SPGW_UDP_CSUM_UPDATE
// Compute outer UDP checksum.
update_checksum_with_payload(gtpu_udp.isValid(),
{
gtpu_ipv4.src_addr,
gtpu_ipv4.dst_addr,
8w0,
gtpu_ipv4.protocol,
gtpu_udp.len,
gtpu_udp.sport,
gtpu_udp.dport,
gtpu_udp.len,
gtpu,
ipv4,
// FIXME: we are assuming only UDP for downlink packets
// How to conditionally switch between UDP/TCP/ICMP?
udp
},
gtpu_udp.checksum,
HashAlgorithm.csum16
);
#endif // WITH_SPGW_UDP_CSUM_UPDATE
}
}
#endif