d0d2555852
For pkts copied to the CPU (to be processed by guest driver), mark the Rx descriptor with flag "OFFLOAD_FWD" to indicate device has already forwarded pkt. The guest driver will use this indicator to avoid duplicate forwarding in the guest OS. Examples include bcast/mcast/unknown ucast pkts flooded to bridged ports. We want to avoid both the device and the guest bridge driver flooding these pkts, which would result in duplicates pkts on the wire. Packet sampling, such as sFlow, can also use this technique to mark pkts for the guest OS to record but otherwise drop. Signed-off-by: Scott Feldman <sfeldma@gmail.com> Message-id: 1435746792-41278-5-git-send-email-sfeldma@gmail.com Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
2631 lines
80 KiB
C
2631 lines
80 KiB
C
/*
|
|
* QEMU rocker switch emulation - OF-DPA flow processing support
|
|
*
|
|
* Copyright (c) 2014 Scott Feldman <sfeldma@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "net/eth.h"
|
|
#include "qemu/iov.h"
|
|
#include "qemu/timer.h"
|
|
#include "qmp-commands.h"
|
|
|
|
#include "rocker.h"
|
|
#include "rocker_hw.h"
|
|
#include "rocker_fp.h"
|
|
#include "rocker_tlv.h"
|
|
#include "rocker_world.h"
|
|
#include "rocker_desc.h"
|
|
#include "rocker_of_dpa.h"
|
|
|
|
static const MACAddr zero_mac = { .a = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };
|
|
static const MACAddr ff_mac = { .a = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } };
|
|
|
|
typedef struct of_dpa {
|
|
World *world;
|
|
GHashTable *flow_tbl;
|
|
GHashTable *group_tbl;
|
|
unsigned int flow_tbl_max_size;
|
|
unsigned int group_tbl_max_size;
|
|
} OfDpa;
|
|
|
|
/* flow_key stolen mostly from OVS
|
|
*
|
|
* Note: fields that compare with network packet header fields
|
|
* are stored in network order (BE) to avoid per-packet field
|
|
* byte-swaps.
|
|
*/
|
|
|
|
typedef struct of_dpa_flow_key {
|
|
uint32_t in_pport; /* ingress port */
|
|
uint32_t tunnel_id; /* overlay tunnel id */
|
|
uint32_t tbl_id; /* table id */
|
|
struct {
|
|
__be16 vlan_id; /* 0 if no VLAN */
|
|
MACAddr src; /* ethernet source address */
|
|
MACAddr dst; /* ethernet destination address */
|
|
__be16 type; /* ethernet frame type */
|
|
} eth;
|
|
struct {
|
|
uint8_t proto; /* IP protocol or ARP opcode */
|
|
uint8_t tos; /* IP ToS */
|
|
uint8_t ttl; /* IP TTL/hop limit */
|
|
uint8_t frag; /* one of FRAG_TYPE_* */
|
|
} ip;
|
|
union {
|
|
struct {
|
|
struct {
|
|
__be32 src; /* IP source address */
|
|
__be32 dst; /* IP destination address */
|
|
} addr;
|
|
union {
|
|
struct {
|
|
__be16 src; /* TCP/UDP/SCTP source port */
|
|
__be16 dst; /* TCP/UDP/SCTP destination port */
|
|
__be16 flags; /* TCP flags */
|
|
} tp;
|
|
struct {
|
|
MACAddr sha; /* ARP source hardware address */
|
|
MACAddr tha; /* ARP target hardware address */
|
|
} arp;
|
|
};
|
|
} ipv4;
|
|
struct {
|
|
struct {
|
|
Ipv6Addr src; /* IPv6 source address */
|
|
Ipv6Addr dst; /* IPv6 destination address */
|
|
} addr;
|
|
__be32 label; /* IPv6 flow label */
|
|
struct {
|
|
__be16 src; /* TCP/UDP/SCTP source port */
|
|
__be16 dst; /* TCP/UDP/SCTP destination port */
|
|
__be16 flags; /* TCP flags */
|
|
} tp;
|
|
struct {
|
|
Ipv6Addr target; /* ND target address */
|
|
MACAddr sll; /* ND source link layer address */
|
|
MACAddr tll; /* ND target link layer address */
|
|
} nd;
|
|
} ipv6;
|
|
};
|
|
int width; /* how many uint64_t's in key? */
|
|
} OfDpaFlowKey;
|
|
|
|
/* Width of key which includes field 'f' in u64s, rounded up */
|
|
#define FLOW_KEY_WIDTH(f) \
|
|
((offsetof(OfDpaFlowKey, f) + \
|
|
sizeof(((OfDpaFlowKey *)0)->f) + \
|
|
sizeof(uint64_t) - 1) / sizeof(uint64_t))
|
|
|
|
typedef struct of_dpa_flow_action {
|
|
uint32_t goto_tbl;
|
|
struct {
|
|
uint32_t group_id;
|
|
uint32_t tun_log_lport;
|
|
__be16 vlan_id;
|
|
} write;
|
|
struct {
|
|
__be16 new_vlan_id;
|
|
uint32_t out_pport;
|
|
uint8_t copy_to_cpu;
|
|
__be16 vlan_id;
|
|
} apply;
|
|
} OfDpaFlowAction;
|
|
|
|
typedef struct of_dpa_flow {
|
|
uint32_t lpm;
|
|
uint32_t priority;
|
|
uint32_t hardtime;
|
|
uint32_t idletime;
|
|
uint64_t cookie;
|
|
OfDpaFlowKey key;
|
|
OfDpaFlowKey mask;
|
|
OfDpaFlowAction action;
|
|
struct {
|
|
uint64_t hits;
|
|
int64_t install_time;
|
|
int64_t refresh_time;
|
|
uint64_t rx_pkts;
|
|
uint64_t tx_pkts;
|
|
} stats;
|
|
} OfDpaFlow;
|
|
|
|
typedef struct of_dpa_flow_pkt_fields {
|
|
uint32_t tunnel_id;
|
|
struct eth_header *ethhdr;
|
|
__be16 *h_proto;
|
|
struct vlan_header *vlanhdr;
|
|
struct ip_header *ipv4hdr;
|
|
struct ip6_header *ipv6hdr;
|
|
Ipv6Addr *ipv6_src_addr;
|
|
Ipv6Addr *ipv6_dst_addr;
|
|
} OfDpaFlowPktFields;
|
|
|
|
typedef struct of_dpa_flow_context {
|
|
uint32_t in_pport;
|
|
uint32_t tunnel_id;
|
|
struct iovec *iov;
|
|
int iovcnt;
|
|
struct eth_header ethhdr_rewrite;
|
|
struct vlan_header vlanhdr_rewrite;
|
|
struct vlan_header vlanhdr;
|
|
OfDpa *of_dpa;
|
|
OfDpaFlowPktFields fields;
|
|
OfDpaFlowAction action_set;
|
|
} OfDpaFlowContext;
|
|
|
|
typedef struct of_dpa_flow_match {
|
|
OfDpaFlowKey value;
|
|
OfDpaFlow *best;
|
|
} OfDpaFlowMatch;
|
|
|
|
typedef struct of_dpa_group {
|
|
uint32_t id;
|
|
union {
|
|
struct {
|
|
uint32_t out_pport;
|
|
uint8_t pop_vlan;
|
|
} l2_interface;
|
|
struct {
|
|
uint32_t group_id;
|
|
MACAddr src_mac;
|
|
MACAddr dst_mac;
|
|
__be16 vlan_id;
|
|
} l2_rewrite;
|
|
struct {
|
|
uint16_t group_count;
|
|
uint32_t *group_ids;
|
|
} l2_flood;
|
|
struct {
|
|
uint32_t group_id;
|
|
MACAddr src_mac;
|
|
MACAddr dst_mac;
|
|
__be16 vlan_id;
|
|
uint8_t ttl_check;
|
|
} l3_unicast;
|
|
};
|
|
} OfDpaGroup;
|
|
|
|
static int of_dpa_mask2prefix(__be32 mask)
|
|
{
|
|
int i;
|
|
int count = 32;
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
if (!(ntohl(mask) & ((2 << i) - 1))) {
|
|
count--;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
#if defined(DEBUG_ROCKER)
|
|
static void of_dpa_flow_key_dump(OfDpaFlowKey *key, OfDpaFlowKey *mask)
|
|
{
|
|
char buf[512], *b = buf, *mac;
|
|
|
|
b += sprintf(b, " tbl %2d", key->tbl_id);
|
|
|
|
if (key->in_pport || (mask && mask->in_pport)) {
|
|
b += sprintf(b, " in_pport %2d", key->in_pport);
|
|
if (mask && mask->in_pport != 0xffffffff) {
|
|
b += sprintf(b, "/0x%08x", key->in_pport);
|
|
}
|
|
}
|
|
|
|
if (key->tunnel_id || (mask && mask->tunnel_id)) {
|
|
b += sprintf(b, " tun %8d", key->tunnel_id);
|
|
if (mask && mask->tunnel_id != 0xffffffff) {
|
|
b += sprintf(b, "/0x%08x", key->tunnel_id);
|
|
}
|
|
}
|
|
|
|
if (key->eth.vlan_id || (mask && mask->eth.vlan_id)) {
|
|
b += sprintf(b, " vlan %4d", ntohs(key->eth.vlan_id));
|
|
if (mask && mask->eth.vlan_id != 0xffff) {
|
|
b += sprintf(b, "/0x%04x", ntohs(key->eth.vlan_id));
|
|
}
|
|
}
|
|
|
|
if (memcmp(key->eth.src.a, zero_mac.a, ETH_ALEN) ||
|
|
(mask && memcmp(mask->eth.src.a, zero_mac.a, ETH_ALEN))) {
|
|
mac = qemu_mac_strdup_printf(key->eth.src.a);
|
|
b += sprintf(b, " src %s", mac);
|
|
g_free(mac);
|
|
if (mask && memcmp(mask->eth.src.a, ff_mac.a, ETH_ALEN)) {
|
|
mac = qemu_mac_strdup_printf(mask->eth.src.a);
|
|
b += sprintf(b, "/%s", mac);
|
|
g_free(mac);
|
|
}
|
|
}
|
|
|
|
if (memcmp(key->eth.dst.a, zero_mac.a, ETH_ALEN) ||
|
|
(mask && memcmp(mask->eth.dst.a, zero_mac.a, ETH_ALEN))) {
|
|
mac = qemu_mac_strdup_printf(key->eth.dst.a);
|
|
b += sprintf(b, " dst %s", mac);
|
|
g_free(mac);
|
|
if (mask && memcmp(mask->eth.dst.a, ff_mac.a, ETH_ALEN)) {
|
|
mac = qemu_mac_strdup_printf(mask->eth.dst.a);
|
|
b += sprintf(b, "/%s", mac);
|
|
g_free(mac);
|
|
}
|
|
}
|
|
|
|
if (key->eth.type || (mask && mask->eth.type)) {
|
|
b += sprintf(b, " type 0x%04x", ntohs(key->eth.type));
|
|
if (mask && mask->eth.type != 0xffff) {
|
|
b += sprintf(b, "/0x%04x", ntohs(mask->eth.type));
|
|
}
|
|
switch (ntohs(key->eth.type)) {
|
|
case 0x0800:
|
|
case 0x86dd:
|
|
if (key->ip.proto || (mask && mask->ip.proto)) {
|
|
b += sprintf(b, " ip proto %2d", key->ip.proto);
|
|
if (mask && mask->ip.proto != 0xff) {
|
|
b += sprintf(b, "/0x%02x", mask->ip.proto);
|
|
}
|
|
}
|
|
if (key->ip.tos || (mask && mask->ip.tos)) {
|
|
b += sprintf(b, " ip tos %2d", key->ip.tos);
|
|
if (mask && mask->ip.tos != 0xff) {
|
|
b += sprintf(b, "/0x%02x", mask->ip.tos);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
switch (ntohs(key->eth.type)) {
|
|
case 0x0800:
|
|
if (key->ipv4.addr.dst || (mask && mask->ipv4.addr.dst)) {
|
|
b += sprintf(b, " dst %s",
|
|
inet_ntoa(*(struct in_addr *)&key->ipv4.addr.dst));
|
|
if (mask) {
|
|
b += sprintf(b, "/%d",
|
|
of_dpa_mask2prefix(mask->ipv4.addr.dst));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
DPRINTF("%s\n", buf);
|
|
}
|
|
#else
|
|
#define of_dpa_flow_key_dump(k, m)
|
|
#endif
|
|
|
|
static void _of_dpa_flow_match(void *key, void *value, void *user_data)
|
|
{
|
|
OfDpaFlow *flow = value;
|
|
OfDpaFlowMatch *match = user_data;
|
|
uint64_t *k = (uint64_t *)&flow->key;
|
|
uint64_t *m = (uint64_t *)&flow->mask;
|
|
uint64_t *v = (uint64_t *)&match->value;
|
|
int i;
|
|
|
|
if (flow->key.tbl_id == match->value.tbl_id) {
|
|
of_dpa_flow_key_dump(&flow->key, &flow->mask);
|
|
}
|
|
|
|
if (flow->key.width > match->value.width) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < flow->key.width; i++, k++, m++, v++) {
|
|
if ((~*k & *m & *v) | (*k & *m & ~*v)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
DPRINTF("match\n");
|
|
|
|
if (!match->best ||
|
|
flow->priority > match->best->priority ||
|
|
flow->lpm > match->best->lpm) {
|
|
match->best = flow;
|
|
}
|
|
}
|
|
|
|
static OfDpaFlow *of_dpa_flow_match(OfDpa *of_dpa, OfDpaFlowMatch *match)
|
|
{
|
|
DPRINTF("\nnew search\n");
|
|
of_dpa_flow_key_dump(&match->value, NULL);
|
|
|
|
g_hash_table_foreach(of_dpa->flow_tbl, _of_dpa_flow_match, match);
|
|
|
|
return match->best;
|
|
}
|
|
|
|
static OfDpaFlow *of_dpa_flow_find(OfDpa *of_dpa, uint64_t cookie)
|
|
{
|
|
return g_hash_table_lookup(of_dpa->flow_tbl, &cookie);
|
|
}
|
|
|
|
static int of_dpa_flow_add(OfDpa *of_dpa, OfDpaFlow *flow)
|
|
{
|
|
g_hash_table_insert(of_dpa->flow_tbl, &flow->cookie, flow);
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static void of_dpa_flow_del(OfDpa *of_dpa, OfDpaFlow *flow)
|
|
{
|
|
g_hash_table_remove(of_dpa->flow_tbl, &flow->cookie);
|
|
}
|
|
|
|
static OfDpaFlow *of_dpa_flow_alloc(uint64_t cookie)
|
|
{
|
|
OfDpaFlow *flow;
|
|
int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) / 1000;
|
|
|
|
flow = g_malloc0(sizeof(OfDpaFlow));
|
|
if (!flow) {
|
|
return NULL;
|
|
}
|
|
|
|
flow->cookie = cookie;
|
|
flow->mask.tbl_id = 0xffffffff;
|
|
|
|
flow->stats.install_time = flow->stats.refresh_time = now;
|
|
|
|
return flow;
|
|
}
|
|
|
|
static void of_dpa_flow_pkt_hdr_reset(OfDpaFlowContext *fc)
|
|
{
|
|
OfDpaFlowPktFields *fields = &fc->fields;
|
|
|
|
fc->iov[0].iov_base = fields->ethhdr;
|
|
fc->iov[0].iov_len = sizeof(struct eth_header);
|
|
fc->iov[1].iov_base = fields->vlanhdr;
|
|
fc->iov[1].iov_len = fields->vlanhdr ? sizeof(struct vlan_header) : 0;
|
|
}
|
|
|
|
static void of_dpa_flow_pkt_parse(OfDpaFlowContext *fc,
|
|
const struct iovec *iov, int iovcnt)
|
|
{
|
|
OfDpaFlowPktFields *fields = &fc->fields;
|
|
size_t sofar = 0;
|
|
int i;
|
|
|
|
sofar += sizeof(struct eth_header);
|
|
if (iov->iov_len < sofar) {
|
|
DPRINTF("flow_pkt_parse underrun on eth_header\n");
|
|
return;
|
|
}
|
|
|
|
fields->ethhdr = iov->iov_base;
|
|
fields->h_proto = &fields->ethhdr->h_proto;
|
|
|
|
if (ntohs(*fields->h_proto) == ETH_P_VLAN) {
|
|
sofar += sizeof(struct vlan_header);
|
|
if (iov->iov_len < sofar) {
|
|
DPRINTF("flow_pkt_parse underrun on vlan_header\n");
|
|
return;
|
|
}
|
|
fields->vlanhdr = (struct vlan_header *)(fields->ethhdr + 1);
|
|
fields->h_proto = &fields->vlanhdr->h_proto;
|
|
}
|
|
|
|
switch (ntohs(*fields->h_proto)) {
|
|
case ETH_P_IP:
|
|
sofar += sizeof(struct ip_header);
|
|
if (iov->iov_len < sofar) {
|
|
DPRINTF("flow_pkt_parse underrun on ip_header\n");
|
|
return;
|
|
}
|
|
fields->ipv4hdr = (struct ip_header *)(fields->h_proto + 1);
|
|
break;
|
|
case ETH_P_IPV6:
|
|
sofar += sizeof(struct ip6_header);
|
|
if (iov->iov_len < sofar) {
|
|
DPRINTF("flow_pkt_parse underrun on ip6_header\n");
|
|
return;
|
|
}
|
|
fields->ipv6hdr = (struct ip6_header *)(fields->h_proto + 1);
|
|
break;
|
|
}
|
|
|
|
/* To facilitate (potential) VLAN tag insertion, Make a
|
|
* copy of the iov and insert two new vectors at the
|
|
* beginning for eth hdr and vlan hdr. No data is copied,
|
|
* just the vectors.
|
|
*/
|
|
|
|
of_dpa_flow_pkt_hdr_reset(fc);
|
|
|
|
fc->iov[2].iov_base = fields->h_proto + 1;
|
|
fc->iov[2].iov_len = iov->iov_len - fc->iov[0].iov_len - fc->iov[1].iov_len;
|
|
|
|
for (i = 1; i < iovcnt; i++) {
|
|
fc->iov[i+2] = iov[i];
|
|
}
|
|
|
|
fc->iovcnt = iovcnt + 2;
|
|
}
|
|
|
|
static void of_dpa_flow_pkt_insert_vlan(OfDpaFlowContext *fc, __be16 vlan_id)
|
|
{
|
|
OfDpaFlowPktFields *fields = &fc->fields;
|
|
uint16_t h_proto = fields->ethhdr->h_proto;
|
|
|
|
if (fields->vlanhdr) {
|
|
DPRINTF("flow_pkt_insert_vlan packet already has vlan\n");
|
|
return;
|
|
}
|
|
|
|
fields->ethhdr->h_proto = htons(ETH_P_VLAN);
|
|
fields->vlanhdr = &fc->vlanhdr;
|
|
fields->vlanhdr->h_tci = vlan_id;
|
|
fields->vlanhdr->h_proto = h_proto;
|
|
fields->h_proto = &fields->vlanhdr->h_proto;
|
|
|
|
fc->iov[1].iov_base = fields->vlanhdr;
|
|
fc->iov[1].iov_len = sizeof(struct vlan_header);
|
|
}
|
|
|
|
static void of_dpa_flow_pkt_strip_vlan(OfDpaFlowContext *fc)
|
|
{
|
|
OfDpaFlowPktFields *fields = &fc->fields;
|
|
|
|
if (!fields->vlanhdr) {
|
|
return;
|
|
}
|
|
|
|
fc->iov[0].iov_len -= sizeof(fields->ethhdr->h_proto);
|
|
fc->iov[1].iov_base = fields->h_proto;
|
|
fc->iov[1].iov_len = sizeof(fields->ethhdr->h_proto);
|
|
}
|
|
|
|
static void of_dpa_flow_pkt_hdr_rewrite(OfDpaFlowContext *fc,
|
|
uint8_t *src_mac, uint8_t *dst_mac,
|
|
__be16 vlan_id)
|
|
{
|
|
OfDpaFlowPktFields *fields = &fc->fields;
|
|
|
|
if (src_mac || dst_mac) {
|
|
memcpy(&fc->ethhdr_rewrite, fields->ethhdr, sizeof(struct eth_header));
|
|
if (src_mac && memcmp(src_mac, zero_mac.a, ETH_ALEN)) {
|
|
memcpy(fc->ethhdr_rewrite.h_source, src_mac, ETH_ALEN);
|
|
}
|
|
if (dst_mac && memcmp(dst_mac, zero_mac.a, ETH_ALEN)) {
|
|
memcpy(fc->ethhdr_rewrite.h_dest, dst_mac, ETH_ALEN);
|
|
}
|
|
fc->iov[0].iov_base = &fc->ethhdr_rewrite;
|
|
}
|
|
|
|
if (vlan_id && fields->vlanhdr) {
|
|
fc->vlanhdr_rewrite = fc->vlanhdr;
|
|
fc->vlanhdr_rewrite.h_tci = vlan_id;
|
|
fc->iov[1].iov_base = &fc->vlanhdr_rewrite;
|
|
}
|
|
}
|
|
|
|
static void of_dpa_flow_ig_tbl(OfDpaFlowContext *fc, uint32_t tbl_id);
|
|
|
|
static void of_dpa_ig_port_build_match(OfDpaFlowContext *fc,
|
|
OfDpaFlowMatch *match)
|
|
{
|
|
match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT;
|
|
match->value.in_pport = fc->in_pport;
|
|
match->value.width = FLOW_KEY_WIDTH(tbl_id);
|
|
}
|
|
|
|
static void of_dpa_ig_port_miss(OfDpaFlowContext *fc)
|
|
{
|
|
uint32_t port;
|
|
|
|
/* The default on miss is for packets from physical ports
|
|
* to go to the VLAN Flow Table. There is no default rule
|
|
* for packets from logical ports, which are dropped on miss.
|
|
*/
|
|
|
|
if (fp_port_from_pport(fc->in_pport, &port)) {
|
|
of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_VLAN);
|
|
}
|
|
}
|
|
|
|
static void of_dpa_vlan_build_match(OfDpaFlowContext *fc,
|
|
OfDpaFlowMatch *match)
|
|
{
|
|
match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_VLAN;
|
|
match->value.in_pport = fc->in_pport;
|
|
if (fc->fields.vlanhdr) {
|
|
match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
|
|
}
|
|
match->value.width = FLOW_KEY_WIDTH(eth.vlan_id);
|
|
}
|
|
|
|
static void of_dpa_vlan_insert(OfDpaFlowContext *fc,
|
|
OfDpaFlow *flow)
|
|
{
|
|
if (flow->action.apply.new_vlan_id) {
|
|
of_dpa_flow_pkt_insert_vlan(fc, flow->action.apply.new_vlan_id);
|
|
}
|
|
}
|
|
|
|
static void of_dpa_term_mac_build_match(OfDpaFlowContext *fc,
|
|
OfDpaFlowMatch *match)
|
|
{
|
|
match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC;
|
|
match->value.in_pport = fc->in_pport;
|
|
match->value.eth.type = *fc->fields.h_proto;
|
|
match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
|
|
memcpy(match->value.eth.dst.a, fc->fields.ethhdr->h_dest,
|
|
sizeof(match->value.eth.dst.a));
|
|
match->value.width = FLOW_KEY_WIDTH(eth.type);
|
|
}
|
|
|
|
static void of_dpa_term_mac_miss(OfDpaFlowContext *fc)
|
|
{
|
|
of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_BRIDGING);
|
|
}
|
|
|
|
static void of_dpa_apply_actions(OfDpaFlowContext *fc,
|
|
OfDpaFlow *flow)
|
|
{
|
|
fc->action_set.apply.copy_to_cpu = flow->action.apply.copy_to_cpu;
|
|
fc->action_set.apply.vlan_id = flow->key.eth.vlan_id;
|
|
}
|
|
|
|
static void of_dpa_bridging_build_match(OfDpaFlowContext *fc,
|
|
OfDpaFlowMatch *match)
|
|
{
|
|
match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_BRIDGING;
|
|
if (fc->fields.vlanhdr) {
|
|
match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
|
|
} else if (fc->tunnel_id) {
|
|
match->value.tunnel_id = fc->tunnel_id;
|
|
}
|
|
memcpy(match->value.eth.dst.a, fc->fields.ethhdr->h_dest,
|
|
sizeof(match->value.eth.dst.a));
|
|
match->value.width = FLOW_KEY_WIDTH(eth.dst);
|
|
}
|
|
|
|
static void of_dpa_bridging_learn(OfDpaFlowContext *fc,
|
|
OfDpaFlow *dst_flow)
|
|
{
|
|
OfDpaFlowMatch match = { { 0, }, };
|
|
OfDpaFlow *flow;
|
|
uint8_t *addr;
|
|
uint16_t vlan_id;
|
|
int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) / 1000;
|
|
int64_t refresh_delay = 1;
|
|
|
|
/* Do a lookup in bridge table by src_mac/vlan */
|
|
|
|
addr = fc->fields.ethhdr->h_source;
|
|
vlan_id = fc->fields.vlanhdr->h_tci;
|
|
|
|
match.value.tbl_id = ROCKER_OF_DPA_TABLE_ID_BRIDGING;
|
|
match.value.eth.vlan_id = vlan_id;
|
|
memcpy(match.value.eth.dst.a, addr, sizeof(match.value.eth.dst.a));
|
|
match.value.width = FLOW_KEY_WIDTH(eth.dst);
|
|
|
|
flow = of_dpa_flow_match(fc->of_dpa, &match);
|
|
if (flow) {
|
|
if (!memcmp(flow->mask.eth.dst.a, ff_mac.a,
|
|
sizeof(flow->mask.eth.dst.a))) {
|
|
/* src_mac/vlan already learned; if in_port and out_port
|
|
* don't match, the end station has moved and the port
|
|
* needs updating */
|
|
/* XXX implement the in_port/out_port check */
|
|
if (now - flow->stats.refresh_time < refresh_delay) {
|
|
return;
|
|
}
|
|
flow->stats.refresh_time = now;
|
|
}
|
|
}
|
|
|
|
/* Let driver know about mac/vlan. This may be a new mac/vlan
|
|
* or a refresh of existing mac/vlan that's been hit after the
|
|
* refresh_delay.
|
|
*/
|
|
|
|
rocker_event_mac_vlan_seen(world_rocker(fc->of_dpa->world),
|
|
fc->in_pport, addr, vlan_id);
|
|
}
|
|
|
|
static void of_dpa_bridging_miss(OfDpaFlowContext *fc)
|
|
{
|
|
of_dpa_bridging_learn(fc, NULL);
|
|
of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_ACL_POLICY);
|
|
}
|
|
|
|
static void of_dpa_bridging_action_write(OfDpaFlowContext *fc,
|
|
OfDpaFlow *flow)
|
|
{
|
|
if (flow->action.write.group_id != ROCKER_GROUP_NONE) {
|
|
fc->action_set.write.group_id = flow->action.write.group_id;
|
|
}
|
|
fc->action_set.write.tun_log_lport = flow->action.write.tun_log_lport;
|
|
}
|
|
|
|
static void of_dpa_unicast_routing_build_match(OfDpaFlowContext *fc,
|
|
OfDpaFlowMatch *match)
|
|
{
|
|
match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING;
|
|
match->value.eth.type = *fc->fields.h_proto;
|
|
if (fc->fields.ipv4hdr) {
|
|
match->value.ipv4.addr.dst = fc->fields.ipv4hdr->ip_dst;
|
|
}
|
|
if (fc->fields.ipv6_dst_addr) {
|
|
memcpy(&match->value.ipv6.addr.dst, fc->fields.ipv6_dst_addr,
|
|
sizeof(match->value.ipv6.addr.dst));
|
|
}
|
|
match->value.width = FLOW_KEY_WIDTH(ipv6.addr.dst);
|
|
}
|
|
|
|
static void of_dpa_unicast_routing_miss(OfDpaFlowContext *fc)
|
|
{
|
|
of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_ACL_POLICY);
|
|
}
|
|
|
|
static void of_dpa_unicast_routing_action_write(OfDpaFlowContext *fc,
|
|
OfDpaFlow *flow)
|
|
{
|
|
if (flow->action.write.group_id != ROCKER_GROUP_NONE) {
|
|
fc->action_set.write.group_id = flow->action.write.group_id;
|
|
}
|
|
}
|
|
|
|
static void
|
|
of_dpa_multicast_routing_build_match(OfDpaFlowContext *fc,
|
|
OfDpaFlowMatch *match)
|
|
{
|
|
match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING;
|
|
match->value.eth.type = *fc->fields.h_proto;
|
|
match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
|
|
if (fc->fields.ipv4hdr) {
|
|
match->value.ipv4.addr.src = fc->fields.ipv4hdr->ip_src;
|
|
match->value.ipv4.addr.dst = fc->fields.ipv4hdr->ip_dst;
|
|
}
|
|
if (fc->fields.ipv6_src_addr) {
|
|
memcpy(&match->value.ipv6.addr.src, fc->fields.ipv6_src_addr,
|
|
sizeof(match->value.ipv6.addr.src));
|
|
}
|
|
if (fc->fields.ipv6_dst_addr) {
|
|
memcpy(&match->value.ipv6.addr.dst, fc->fields.ipv6_dst_addr,
|
|
sizeof(match->value.ipv6.addr.dst));
|
|
}
|
|
match->value.width = FLOW_KEY_WIDTH(ipv6.addr.dst);
|
|
}
|
|
|
|
static void of_dpa_multicast_routing_miss(OfDpaFlowContext *fc)
|
|
{
|
|
of_dpa_flow_ig_tbl(fc, ROCKER_OF_DPA_TABLE_ID_ACL_POLICY);
|
|
}
|
|
|
|
static void
|
|
of_dpa_multicast_routing_action_write(OfDpaFlowContext *fc,
|
|
OfDpaFlow *flow)
|
|
{
|
|
if (flow->action.write.group_id != ROCKER_GROUP_NONE) {
|
|
fc->action_set.write.group_id = flow->action.write.group_id;
|
|
}
|
|
fc->action_set.write.vlan_id = flow->action.write.vlan_id;
|
|
}
|
|
|
|
static void of_dpa_acl_build_match(OfDpaFlowContext *fc,
|
|
OfDpaFlowMatch *match)
|
|
{
|
|
match->value.tbl_id = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY;
|
|
match->value.in_pport = fc->in_pport;
|
|
memcpy(match->value.eth.src.a, fc->fields.ethhdr->h_source,
|
|
sizeof(match->value.eth.src.a));
|
|
memcpy(match->value.eth.dst.a, fc->fields.ethhdr->h_dest,
|
|
sizeof(match->value.eth.dst.a));
|
|
match->value.eth.type = *fc->fields.h_proto;
|
|
match->value.eth.vlan_id = fc->fields.vlanhdr->h_tci;
|
|
match->value.width = FLOW_KEY_WIDTH(eth.type);
|
|
if (fc->fields.ipv4hdr) {
|
|
match->value.ip.proto = fc->fields.ipv4hdr->ip_p;
|
|
match->value.ip.tos = fc->fields.ipv4hdr->ip_tos;
|
|
match->value.width = FLOW_KEY_WIDTH(ip.tos);
|
|
} else if (fc->fields.ipv6hdr) {
|
|
match->value.ip.proto =
|
|
fc->fields.ipv6hdr->ip6_ctlun.ip6_un1.ip6_un1_nxt;
|
|
match->value.ip.tos = 0; /* XXX what goes here? */
|
|
match->value.width = FLOW_KEY_WIDTH(ip.tos);
|
|
}
|
|
}
|
|
|
|
static void of_dpa_eg(OfDpaFlowContext *fc);
|
|
static void of_dpa_acl_hit(OfDpaFlowContext *fc,
|
|
OfDpaFlow *dst_flow)
|
|
{
|
|
of_dpa_eg(fc);
|
|
}
|
|
|
|
static void of_dpa_acl_action_write(OfDpaFlowContext *fc,
|
|
OfDpaFlow *flow)
|
|
{
|
|
if (flow->action.write.group_id != ROCKER_GROUP_NONE) {
|
|
fc->action_set.write.group_id = flow->action.write.group_id;
|
|
}
|
|
}
|
|
|
|
static void of_dpa_drop(OfDpaFlowContext *fc)
|
|
{
|
|
/* drop packet */
|
|
}
|
|
|
|
static OfDpaGroup *of_dpa_group_find(OfDpa *of_dpa,
|
|
uint32_t group_id)
|
|
{
|
|
return g_hash_table_lookup(of_dpa->group_tbl, &group_id);
|
|
}
|
|
|
|
static int of_dpa_group_add(OfDpa *of_dpa, OfDpaGroup *group)
|
|
{
|
|
g_hash_table_insert(of_dpa->group_tbl, &group->id, group);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
static int of_dpa_group_mod(OfDpa *of_dpa, OfDpaGroup *group)
|
|
{
|
|
OfDpaGroup *old_group = of_dpa_group_find(of_dpa, group->id);
|
|
|
|
if (!old_group) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* XXX */
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int of_dpa_group_del(OfDpa *of_dpa, OfDpaGroup *group)
|
|
{
|
|
g_hash_table_remove(of_dpa->group_tbl, &group->id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
static int of_dpa_group_get_stats(OfDpa *of_dpa, uint32_t id)
|
|
{
|
|
OfDpaGroup *group = of_dpa_group_find(of_dpa, id);
|
|
|
|
if (!group) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* XXX get/return stats */
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static OfDpaGroup *of_dpa_group_alloc(uint32_t id)
|
|
{
|
|
OfDpaGroup *group = g_malloc0(sizeof(OfDpaGroup));
|
|
|
|
if (!group) {
|
|
return NULL;
|
|
}
|
|
|
|
group->id = id;
|
|
|
|
return group;
|
|
}
|
|
|
|
static void of_dpa_output_l2_interface(OfDpaFlowContext *fc,
|
|
OfDpaGroup *group)
|
|
{
|
|
uint8_t copy_to_cpu = fc->action_set.apply.copy_to_cpu;
|
|
|
|
if (group->l2_interface.pop_vlan) {
|
|
of_dpa_flow_pkt_strip_vlan(fc);
|
|
}
|
|
|
|
/* Note: By default, and as per the OpenFlow 1.3.1
|
|
* specification, a packet cannot be forwarded back
|
|
* to the IN_PORT from which it came in. An action
|
|
* bucket that specifies the particular packet's
|
|
* egress port is not evaluated.
|
|
*/
|
|
|
|
if (group->l2_interface.out_pport == 0) {
|
|
rx_produce(fc->of_dpa->world, fc->in_pport, fc->iov, fc->iovcnt,
|
|
copy_to_cpu);
|
|
} else if (group->l2_interface.out_pport != fc->in_pport) {
|
|
rocker_port_eg(world_rocker(fc->of_dpa->world),
|
|
group->l2_interface.out_pport,
|
|
fc->iov, fc->iovcnt);
|
|
}
|
|
}
|
|
|
|
static void of_dpa_output_l2_rewrite(OfDpaFlowContext *fc,
|
|
OfDpaGroup *group)
|
|
{
|
|
OfDpaGroup *l2_group =
|
|
of_dpa_group_find(fc->of_dpa, group->l2_rewrite.group_id);
|
|
|
|
if (!l2_group) {
|
|
return;
|
|
}
|
|
|
|
of_dpa_flow_pkt_hdr_rewrite(fc, group->l2_rewrite.src_mac.a,
|
|
group->l2_rewrite.dst_mac.a,
|
|
group->l2_rewrite.vlan_id);
|
|
of_dpa_output_l2_interface(fc, l2_group);
|
|
}
|
|
|
|
static void of_dpa_output_l2_flood(OfDpaFlowContext *fc,
|
|
OfDpaGroup *group)
|
|
{
|
|
OfDpaGroup *l2_group;
|
|
int i;
|
|
|
|
for (i = 0; i < group->l2_flood.group_count; i++) {
|
|
of_dpa_flow_pkt_hdr_reset(fc);
|
|
l2_group = of_dpa_group_find(fc->of_dpa, group->l2_flood.group_ids[i]);
|
|
if (!l2_group) {
|
|
continue;
|
|
}
|
|
switch (ROCKER_GROUP_TYPE_GET(l2_group->id)) {
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE:
|
|
of_dpa_output_l2_interface(fc, l2_group);
|
|
break;
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE:
|
|
of_dpa_output_l2_rewrite(fc, l2_group);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void of_dpa_output_l3_unicast(OfDpaFlowContext *fc, OfDpaGroup *group)
|
|
{
|
|
OfDpaGroup *l2_group =
|
|
of_dpa_group_find(fc->of_dpa, group->l3_unicast.group_id);
|
|
|
|
if (!l2_group) {
|
|
return;
|
|
}
|
|
|
|
of_dpa_flow_pkt_hdr_rewrite(fc, group->l3_unicast.src_mac.a,
|
|
group->l3_unicast.dst_mac.a,
|
|
group->l3_unicast.vlan_id);
|
|
/* XXX need ttl_check */
|
|
of_dpa_output_l2_interface(fc, l2_group);
|
|
}
|
|
|
|
static void of_dpa_eg(OfDpaFlowContext *fc)
|
|
{
|
|
OfDpaFlowAction *set = &fc->action_set;
|
|
OfDpaGroup *group;
|
|
uint32_t group_id;
|
|
|
|
/* send a copy of pkt to CPU (controller)? */
|
|
|
|
if (set->apply.copy_to_cpu) {
|
|
group_id = ROCKER_GROUP_L2_INTERFACE(set->apply.vlan_id, 0);
|
|
group = of_dpa_group_find(fc->of_dpa, group_id);
|
|
if (group) {
|
|
of_dpa_output_l2_interface(fc, group);
|
|
of_dpa_flow_pkt_hdr_reset(fc);
|
|
}
|
|
}
|
|
|
|
/* process group write actions */
|
|
|
|
if (!set->write.group_id) {
|
|
return;
|
|
}
|
|
|
|
group = of_dpa_group_find(fc->of_dpa, set->write.group_id);
|
|
if (!group) {
|
|
return;
|
|
}
|
|
|
|
switch (ROCKER_GROUP_TYPE_GET(group->id)) {
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE:
|
|
of_dpa_output_l2_interface(fc, group);
|
|
break;
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE:
|
|
of_dpa_output_l2_rewrite(fc, group);
|
|
break;
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD:
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST:
|
|
of_dpa_output_l2_flood(fc, group);
|
|
break;
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST:
|
|
of_dpa_output_l3_unicast(fc, group);
|
|
break;
|
|
}
|
|
}
|
|
|
|
typedef struct of_dpa_flow_tbl_ops {
|
|
void (*build_match)(OfDpaFlowContext *fc, OfDpaFlowMatch *match);
|
|
void (*hit)(OfDpaFlowContext *fc, OfDpaFlow *flow);
|
|
void (*miss)(OfDpaFlowContext *fc);
|
|
void (*hit_no_goto)(OfDpaFlowContext *fc);
|
|
void (*action_apply)(OfDpaFlowContext *fc, OfDpaFlow *flow);
|
|
void (*action_write)(OfDpaFlowContext *fc, OfDpaFlow *flow);
|
|
} OfDpaFlowTblOps;
|
|
|
|
static OfDpaFlowTblOps of_dpa_tbl_ops[] = {
|
|
[ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT] = {
|
|
.build_match = of_dpa_ig_port_build_match,
|
|
.miss = of_dpa_ig_port_miss,
|
|
.hit_no_goto = of_dpa_drop,
|
|
},
|
|
[ROCKER_OF_DPA_TABLE_ID_VLAN] = {
|
|
.build_match = of_dpa_vlan_build_match,
|
|
.hit_no_goto = of_dpa_drop,
|
|
.action_apply = of_dpa_vlan_insert,
|
|
},
|
|
[ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC] = {
|
|
.build_match = of_dpa_term_mac_build_match,
|
|
.miss = of_dpa_term_mac_miss,
|
|
.hit_no_goto = of_dpa_drop,
|
|
.action_apply = of_dpa_apply_actions,
|
|
},
|
|
[ROCKER_OF_DPA_TABLE_ID_BRIDGING] = {
|
|
.build_match = of_dpa_bridging_build_match,
|
|
.hit = of_dpa_bridging_learn,
|
|
.miss = of_dpa_bridging_miss,
|
|
.hit_no_goto = of_dpa_drop,
|
|
.action_apply = of_dpa_apply_actions,
|
|
.action_write = of_dpa_bridging_action_write,
|
|
},
|
|
[ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING] = {
|
|
.build_match = of_dpa_unicast_routing_build_match,
|
|
.miss = of_dpa_unicast_routing_miss,
|
|
.hit_no_goto = of_dpa_drop,
|
|
.action_write = of_dpa_unicast_routing_action_write,
|
|
},
|
|
[ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING] = {
|
|
.build_match = of_dpa_multicast_routing_build_match,
|
|
.miss = of_dpa_multicast_routing_miss,
|
|
.hit_no_goto = of_dpa_drop,
|
|
.action_write = of_dpa_multicast_routing_action_write,
|
|
},
|
|
[ROCKER_OF_DPA_TABLE_ID_ACL_POLICY] = {
|
|
.build_match = of_dpa_acl_build_match,
|
|
.hit = of_dpa_acl_hit,
|
|
.miss = of_dpa_eg,
|
|
.action_apply = of_dpa_apply_actions,
|
|
.action_write = of_dpa_acl_action_write,
|
|
},
|
|
};
|
|
|
|
static void of_dpa_flow_ig_tbl(OfDpaFlowContext *fc, uint32_t tbl_id)
|
|
{
|
|
OfDpaFlowTblOps *ops = &of_dpa_tbl_ops[tbl_id];
|
|
OfDpaFlowMatch match = { { 0, }, };
|
|
OfDpaFlow *flow;
|
|
|
|
if (ops->build_match) {
|
|
ops->build_match(fc, &match);
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
flow = of_dpa_flow_match(fc->of_dpa, &match);
|
|
if (!flow) {
|
|
if (ops->miss) {
|
|
ops->miss(fc);
|
|
}
|
|
return;
|
|
}
|
|
|
|
flow->stats.hits++;
|
|
|
|
if (ops->action_apply) {
|
|
ops->action_apply(fc, flow);
|
|
}
|
|
|
|
if (ops->action_write) {
|
|
ops->action_write(fc, flow);
|
|
}
|
|
|
|
if (ops->hit) {
|
|
ops->hit(fc, flow);
|
|
}
|
|
|
|
if (flow->action.goto_tbl) {
|
|
of_dpa_flow_ig_tbl(fc, flow->action.goto_tbl);
|
|
} else if (ops->hit_no_goto) {
|
|
ops->hit_no_goto(fc);
|
|
}
|
|
|
|
/* drop packet */
|
|
}
|
|
|
|
static ssize_t of_dpa_ig(World *world, uint32_t pport,
|
|
const struct iovec *iov, int iovcnt)
|
|
{
|
|
struct iovec iov_copy[iovcnt + 2];
|
|
OfDpaFlowContext fc = {
|
|
.of_dpa = world_private(world),
|
|
.in_pport = pport,
|
|
.iov = iov_copy,
|
|
.iovcnt = iovcnt + 2,
|
|
};
|
|
|
|
of_dpa_flow_pkt_parse(&fc, iov, iovcnt);
|
|
of_dpa_flow_ig_tbl(&fc, ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT);
|
|
|
|
return iov_size(iov, iovcnt);
|
|
}
|
|
|
|
#define ROCKER_TUNNEL_LPORT 0x00010000
|
|
|
|
static int of_dpa_cmd_add_ig_port(OfDpaFlow *flow, RockerTlv **flow_tlvs)
|
|
{
|
|
OfDpaFlowKey *key = &flow->key;
|
|
OfDpaFlowKey *mask = &flow->mask;
|
|
OfDpaFlowAction *action = &flow->action;
|
|
bool overlay_tunnel;
|
|
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT] ||
|
|
!flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
key->tbl_id = ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT;
|
|
key->width = FLOW_KEY_WIDTH(tbl_id);
|
|
|
|
key->in_pport = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT]);
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]) {
|
|
mask->in_pport =
|
|
rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]);
|
|
}
|
|
|
|
overlay_tunnel = !!(key->in_pport & ROCKER_TUNNEL_LPORT);
|
|
|
|
action->goto_tbl =
|
|
rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
|
|
|
|
if (!overlay_tunnel && action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_VLAN) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
if (overlay_tunnel && action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_BRIDGING) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static int of_dpa_cmd_add_vlan(OfDpaFlow *flow, RockerTlv **flow_tlvs)
|
|
{
|
|
OfDpaFlowKey *key = &flow->key;
|
|
OfDpaFlowKey *mask = &flow->mask;
|
|
OfDpaFlowAction *action = &flow->action;
|
|
uint32_t port;
|
|
bool untagged;
|
|
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT] ||
|
|
!flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
|
|
DPRINTF("Must give in_pport and vlan_id to install VLAN tbl entry\n");
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
key->tbl_id = ROCKER_OF_DPA_TABLE_ID_VLAN;
|
|
key->width = FLOW_KEY_WIDTH(eth.vlan_id);
|
|
|
|
key->in_pport = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT]);
|
|
if (!fp_port_from_pport(key->in_pport, &port)) {
|
|
DPRINTF("in_pport (%d) not a front-panel port\n", key->in_pport);
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
mask->in_pport = 0xffffffff;
|
|
|
|
key->eth.vlan_id = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]) {
|
|
mask->eth.vlan_id =
|
|
rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]);
|
|
}
|
|
|
|
if (key->eth.vlan_id) {
|
|
untagged = false; /* filtering */
|
|
} else {
|
|
untagged = true;
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
|
|
action->goto_tbl =
|
|
rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
|
|
if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC) {
|
|
DPRINTF("Goto tbl (%d) must be TERM_MAC\n", action->goto_tbl);
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
}
|
|
|
|
if (untagged) {
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_NEW_VLAN_ID]) {
|
|
DPRINTF("Must specify new vlan_id if untagged\n");
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
action->apply.new_vlan_id =
|
|
rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_NEW_VLAN_ID]);
|
|
if (1 > ntohs(action->apply.new_vlan_id) ||
|
|
ntohs(action->apply.new_vlan_id) > 4095) {
|
|
DPRINTF("New vlan_id (%d) must be between 1 and 4095\n",
|
|
ntohs(action->apply.new_vlan_id));
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
}
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static int of_dpa_cmd_add_term_mac(OfDpaFlow *flow, RockerTlv **flow_tlvs)
|
|
{
|
|
OfDpaFlowKey *key = &flow->key;
|
|
OfDpaFlowKey *mask = &flow->mask;
|
|
OfDpaFlowAction *action = &flow->action;
|
|
const MACAddr ipv4_mcast = { .a = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x00 } };
|
|
const MACAddr ipv4_mask = { .a = { 0xff, 0xff, 0xff, 0x80, 0x00, 0x00 } };
|
|
const MACAddr ipv6_mcast = { .a = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x00 } };
|
|
const MACAddr ipv6_mask = { .a = { 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } };
|
|
uint32_t port;
|
|
bool unicast = false;
|
|
bool multicast = false;
|
|
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT] ||
|
|
!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK] ||
|
|
!flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE] ||
|
|
!flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC] ||
|
|
!flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK] ||
|
|
!flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID] ||
|
|
!flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
key->tbl_id = ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC;
|
|
key->width = FLOW_KEY_WIDTH(eth.type);
|
|
|
|
key->in_pport = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT]);
|
|
if (!fp_port_from_pport(key->in_pport, &port)) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
mask->in_pport =
|
|
rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]);
|
|
|
|
key->eth.type = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]);
|
|
if (key->eth.type != htons(0x0800) && key->eth.type != htons(0x86dd)) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
mask->eth.type = htons(0xffff);
|
|
|
|
memcpy(key->eth.dst.a,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
|
|
sizeof(key->eth.dst.a));
|
|
memcpy(mask->eth.dst.a,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]),
|
|
sizeof(mask->eth.dst.a));
|
|
|
|
if ((key->eth.dst.a[0] & 0x01) == 0x00) {
|
|
unicast = true;
|
|
}
|
|
|
|
/* only two wildcard rules are acceptable for IPv4 and IPv6 multicast */
|
|
if (memcmp(key->eth.dst.a, ipv4_mcast.a, sizeof(key->eth.dst.a)) == 0 &&
|
|
memcmp(mask->eth.dst.a, ipv4_mask.a, sizeof(mask->eth.dst.a)) == 0) {
|
|
multicast = true;
|
|
}
|
|
if (memcmp(key->eth.dst.a, ipv6_mcast.a, sizeof(key->eth.dst.a)) == 0 &&
|
|
memcmp(mask->eth.dst.a, ipv6_mask.a, sizeof(mask->eth.dst.a)) == 0) {
|
|
multicast = true;
|
|
}
|
|
|
|
if (!unicast && !multicast) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
key->eth.vlan_id = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
|
|
mask->eth.vlan_id =
|
|
rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]);
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
|
|
action->goto_tbl =
|
|
rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
|
|
|
|
if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING &&
|
|
action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
if (unicast &&
|
|
action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
if (multicast &&
|
|
action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]) {
|
|
action->apply.copy_to_cpu =
|
|
rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]);
|
|
}
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static int of_dpa_cmd_add_bridging(OfDpaFlow *flow, RockerTlv **flow_tlvs)
|
|
{
|
|
OfDpaFlowKey *key = &flow->key;
|
|
OfDpaFlowKey *mask = &flow->mask;
|
|
OfDpaFlowAction *action = &flow->action;
|
|
bool unicast = false;
|
|
bool dst_mac = false;
|
|
bool dst_mac_mask = false;
|
|
enum {
|
|
BRIDGING_MODE_UNKNOWN,
|
|
BRIDGING_MODE_VLAN_UCAST,
|
|
BRIDGING_MODE_VLAN_MCAST,
|
|
BRIDGING_MODE_VLAN_DFLT,
|
|
BRIDGING_MODE_TUNNEL_UCAST,
|
|
BRIDGING_MODE_TUNNEL_MCAST,
|
|
BRIDGING_MODE_TUNNEL_DFLT,
|
|
} mode = BRIDGING_MODE_UNKNOWN;
|
|
|
|
key->tbl_id = ROCKER_OF_DPA_TABLE_ID_BRIDGING;
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
|
|
key->eth.vlan_id =
|
|
rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
|
|
mask->eth.vlan_id = 0xffff;
|
|
key->width = FLOW_KEY_WIDTH(eth.vlan_id);
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_ID]) {
|
|
key->tunnel_id =
|
|
rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_ID]);
|
|
mask->tunnel_id = 0xffffffff;
|
|
key->width = FLOW_KEY_WIDTH(tunnel_id);
|
|
}
|
|
|
|
/* can't do VLAN bridging and tunnel bridging at same time */
|
|
if (key->eth.vlan_id && key->tunnel_id) {
|
|
DPRINTF("can't do VLAN bridging and tunnel bridging at same time\n");
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
|
|
memcpy(key->eth.dst.a,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
|
|
sizeof(key->eth.dst.a));
|
|
key->width = FLOW_KEY_WIDTH(eth.dst);
|
|
dst_mac = true;
|
|
unicast = (key->eth.dst.a[0] & 0x01) == 0x00;
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]) {
|
|
memcpy(mask->eth.dst.a,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]),
|
|
sizeof(mask->eth.dst.a));
|
|
key->width = FLOW_KEY_WIDTH(eth.dst);
|
|
dst_mac_mask = true;
|
|
} else if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
|
|
memcpy(mask->eth.dst.a, ff_mac.a, sizeof(mask->eth.dst.a));
|
|
}
|
|
|
|
if (key->eth.vlan_id) {
|
|
if (dst_mac && !dst_mac_mask) {
|
|
mode = unicast ? BRIDGING_MODE_VLAN_UCAST :
|
|
BRIDGING_MODE_VLAN_MCAST;
|
|
} else if ((dst_mac && dst_mac_mask) || !dst_mac) {
|
|
mode = BRIDGING_MODE_VLAN_DFLT;
|
|
}
|
|
} else if (key->tunnel_id) {
|
|
if (dst_mac && !dst_mac_mask) {
|
|
mode = unicast ? BRIDGING_MODE_TUNNEL_UCAST :
|
|
BRIDGING_MODE_TUNNEL_MCAST;
|
|
} else if ((dst_mac && dst_mac_mask) || !dst_mac) {
|
|
mode = BRIDGING_MODE_TUNNEL_DFLT;
|
|
}
|
|
}
|
|
|
|
if (mode == BRIDGING_MODE_UNKNOWN) {
|
|
DPRINTF("Unknown bridging mode\n");
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
|
|
action->goto_tbl =
|
|
rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
|
|
if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_ACL_POLICY) {
|
|
DPRINTF("Briding goto tbl must be ACL policy\n");
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
|
|
action->write.group_id =
|
|
rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
|
|
switch (mode) {
|
|
case BRIDGING_MODE_VLAN_UCAST:
|
|
if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
|
|
ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE) {
|
|
DPRINTF("Bridging mode vlan ucast needs L2 "
|
|
"interface group (0x%08x)\n",
|
|
action->write.group_id);
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
break;
|
|
case BRIDGING_MODE_VLAN_MCAST:
|
|
if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
|
|
ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST) {
|
|
DPRINTF("Bridging mode vlan mcast needs L2 "
|
|
"mcast group (0x%08x)\n",
|
|
action->write.group_id);
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
break;
|
|
case BRIDGING_MODE_VLAN_DFLT:
|
|
if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
|
|
ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD) {
|
|
DPRINTF("Bridging mode vlan dflt needs L2 "
|
|
"flood group (0x%08x)\n",
|
|
action->write.group_id);
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
break;
|
|
case BRIDGING_MODE_TUNNEL_MCAST:
|
|
if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
|
|
ROCKER_OF_DPA_GROUP_TYPE_L2_OVERLAY) {
|
|
DPRINTF("Bridging mode tunnel mcast needs L2 "
|
|
"overlay group (0x%08x)\n",
|
|
action->write.group_id);
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
break;
|
|
case BRIDGING_MODE_TUNNEL_DFLT:
|
|
if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
|
|
ROCKER_OF_DPA_GROUP_TYPE_L2_OVERLAY) {
|
|
DPRINTF("Bridging mode tunnel dflt needs L2 "
|
|
"overlay group (0x%08x)\n",
|
|
action->write.group_id);
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
break;
|
|
default:
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_LPORT]) {
|
|
action->write.tun_log_lport =
|
|
rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_LPORT]);
|
|
if (mode != BRIDGING_MODE_TUNNEL_UCAST) {
|
|
DPRINTF("Have tunnel logical port but not "
|
|
"in bridging tunnel mode\n");
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]) {
|
|
action->apply.copy_to_cpu =
|
|
rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]);
|
|
}
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static int of_dpa_cmd_add_unicast_routing(OfDpaFlow *flow,
|
|
RockerTlv **flow_tlvs)
|
|
{
|
|
OfDpaFlowKey *key = &flow->key;
|
|
OfDpaFlowKey *mask = &flow->mask;
|
|
OfDpaFlowAction *action = &flow->action;
|
|
enum {
|
|
UNICAST_ROUTING_MODE_UNKNOWN,
|
|
UNICAST_ROUTING_MODE_IPV4,
|
|
UNICAST_ROUTING_MODE_IPV6,
|
|
} mode = UNICAST_ROUTING_MODE_UNKNOWN;
|
|
uint8_t type;
|
|
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
key->tbl_id = ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING;
|
|
key->width = FLOW_KEY_WIDTH(ipv6.addr.dst);
|
|
|
|
key->eth.type = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]);
|
|
switch (ntohs(key->eth.type)) {
|
|
case 0x0800:
|
|
mode = UNICAST_ROUTING_MODE_IPV4;
|
|
break;
|
|
case 0x86dd:
|
|
mode = UNICAST_ROUTING_MODE_IPV6;
|
|
break;
|
|
default:
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
mask->eth.type = htons(0xffff);
|
|
|
|
switch (mode) {
|
|
case UNICAST_ROUTING_MODE_IPV4:
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
key->ipv4.addr.dst =
|
|
rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP]);
|
|
if (ipv4_addr_is_multicast(key->ipv4.addr.dst)) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
flow->lpm = of_dpa_mask2prefix(htonl(0xffffffff));
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP_MASK]) {
|
|
mask->ipv4.addr.dst =
|
|
rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP_MASK]);
|
|
flow->lpm = of_dpa_mask2prefix(mask->ipv4.addr.dst);
|
|
}
|
|
break;
|
|
case UNICAST_ROUTING_MODE_IPV6:
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
memcpy(&key->ipv6.addr.dst,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6]),
|
|
sizeof(key->ipv6.addr.dst));
|
|
if (ipv6_addr_is_multicast(&key->ipv6.addr.dst)) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6_MASK]) {
|
|
memcpy(&mask->ipv6.addr.dst,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6_MASK]),
|
|
sizeof(mask->ipv6.addr.dst));
|
|
}
|
|
break;
|
|
default:
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
|
|
action->goto_tbl =
|
|
rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
|
|
if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_ACL_POLICY) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
|
|
action->write.group_id =
|
|
rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
|
|
type = ROCKER_GROUP_TYPE_GET(action->write.group_id);
|
|
if (type != ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE &&
|
|
type != ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST &&
|
|
type != ROCKER_OF_DPA_GROUP_TYPE_L3_ECMP) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
}
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static int of_dpa_cmd_add_multicast_routing(OfDpaFlow *flow,
|
|
RockerTlv **flow_tlvs)
|
|
{
|
|
OfDpaFlowKey *key = &flow->key;
|
|
OfDpaFlowKey *mask = &flow->mask;
|
|
OfDpaFlowAction *action = &flow->action;
|
|
enum {
|
|
MULTICAST_ROUTING_MODE_UNKNOWN,
|
|
MULTICAST_ROUTING_MODE_IPV4,
|
|
MULTICAST_ROUTING_MODE_IPV6,
|
|
} mode = MULTICAST_ROUTING_MODE_UNKNOWN;
|
|
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE] ||
|
|
!flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
key->tbl_id = ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING;
|
|
key->width = FLOW_KEY_WIDTH(ipv6.addr.dst);
|
|
|
|
key->eth.type = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]);
|
|
switch (ntohs(key->eth.type)) {
|
|
case 0x0800:
|
|
mode = MULTICAST_ROUTING_MODE_IPV4;
|
|
break;
|
|
case 0x86dd:
|
|
mode = MULTICAST_ROUTING_MODE_IPV6;
|
|
break;
|
|
default:
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
key->eth.vlan_id = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
|
|
|
|
switch (mode) {
|
|
case MULTICAST_ROUTING_MODE_IPV4:
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP]) {
|
|
key->ipv4.addr.src =
|
|
rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP]);
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP_MASK]) {
|
|
mask->ipv4.addr.src =
|
|
rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP_MASK]);
|
|
}
|
|
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IP]) {
|
|
if (mask->ipv4.addr.src != 0) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
}
|
|
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
key->ipv4.addr.dst =
|
|
rocker_tlv_get_u32(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IP]);
|
|
if (!ipv4_addr_is_multicast(key->ipv4.addr.dst)) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
break;
|
|
|
|
case MULTICAST_ROUTING_MODE_IPV6:
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6]) {
|
|
memcpy(&key->ipv6.addr.src,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6]),
|
|
sizeof(key->ipv6.addr.src));
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6_MASK]) {
|
|
memcpy(&mask->ipv6.addr.src,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6_MASK]),
|
|
sizeof(mask->ipv6.addr.src));
|
|
}
|
|
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_SRC_IPV6]) {
|
|
if (mask->ipv6.addr.src.addr32[0] != 0 &&
|
|
mask->ipv6.addr.src.addr32[1] != 0 &&
|
|
mask->ipv6.addr.src.addr32[2] != 0 &&
|
|
mask->ipv6.addr.src.addr32[3] != 0) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
}
|
|
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
memcpy(&key->ipv6.addr.dst,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_IPV6]),
|
|
sizeof(key->ipv6.addr.dst));
|
|
if (!ipv6_addr_is_multicast(&key->ipv6.addr.dst)) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]) {
|
|
action->goto_tbl =
|
|
rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_GOTO_TABLE_ID]);
|
|
if (action->goto_tbl != ROCKER_OF_DPA_TABLE_ID_ACL_POLICY) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
|
|
action->write.group_id =
|
|
rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
|
|
if (ROCKER_GROUP_TYPE_GET(action->write.group_id) !=
|
|
ROCKER_OF_DPA_GROUP_TYPE_L3_MCAST) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
action->write.vlan_id = key->eth.vlan_id;
|
|
}
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static int of_dpa_cmd_add_acl_ip(OfDpaFlowKey *key, OfDpaFlowKey *mask,
|
|
RockerTlv **flow_tlvs)
|
|
{
|
|
key->width = FLOW_KEY_WIDTH(ip.tos);
|
|
|
|
key->ip.proto = 0;
|
|
key->ip.tos = 0;
|
|
mask->ip.proto = 0;
|
|
mask->ip.tos = 0;
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_PROTO]) {
|
|
key->ip.proto =
|
|
rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_PROTO]);
|
|
}
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_PROTO_MASK]) {
|
|
mask->ip.proto =
|
|
rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_PROTO_MASK]);
|
|
}
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_DSCP]) {
|
|
key->ip.tos =
|
|
rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_DSCP]);
|
|
}
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_DSCP_MASK]) {
|
|
mask->ip.tos =
|
|
rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_DSCP_MASK]);
|
|
}
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_ECN]) {
|
|
key->ip.tos |=
|
|
rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_ECN]) << 6;
|
|
}
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_IP_ECN_MASK]) {
|
|
mask->ip.tos |=
|
|
rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_IP_ECN_MASK]) << 6;
|
|
}
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static int of_dpa_cmd_add_acl(OfDpaFlow *flow, RockerTlv **flow_tlvs)
|
|
{
|
|
OfDpaFlowKey *key = &flow->key;
|
|
OfDpaFlowKey *mask = &flow->mask;
|
|
OfDpaFlowAction *action = &flow->action;
|
|
enum {
|
|
ACL_MODE_UNKNOWN,
|
|
ACL_MODE_IPV4_VLAN,
|
|
ACL_MODE_IPV6_VLAN,
|
|
ACL_MODE_IPV4_TENANT,
|
|
ACL_MODE_IPV6_TENANT,
|
|
ACL_MODE_NON_IP_VLAN,
|
|
ACL_MODE_NON_IP_TENANT,
|
|
ACL_MODE_ANY_VLAN,
|
|
ACL_MODE_ANY_TENANT,
|
|
} mode = ACL_MODE_UNKNOWN;
|
|
int err = ROCKER_OK;
|
|
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT] ||
|
|
!flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID] &&
|
|
flow_tlvs[ROCKER_TLV_OF_DPA_TUNNEL_ID]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
key->tbl_id = ROCKER_OF_DPA_TABLE_ID_ACL_POLICY;
|
|
key->width = FLOW_KEY_WIDTH(eth.type);
|
|
|
|
key->in_pport = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT]);
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]) {
|
|
mask->in_pport =
|
|
rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IN_PPORT_MASK]);
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]) {
|
|
memcpy(key->eth.src.a,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]),
|
|
sizeof(key->eth.src.a));
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC_MASK]) {
|
|
memcpy(mask->eth.src.a,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC_MASK]),
|
|
sizeof(mask->eth.src.a));
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
|
|
memcpy(key->eth.dst.a,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
|
|
sizeof(key->eth.dst.a));
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]) {
|
|
memcpy(mask->eth.dst.a,
|
|
rocker_tlv_data(flow_tlvs[ROCKER_TLV_OF_DPA_DST_MAC_MASK]),
|
|
sizeof(mask->eth.dst.a));
|
|
}
|
|
|
|
key->eth.type = rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_ETHERTYPE]);
|
|
if (key->eth.type) {
|
|
mask->eth.type = 0xffff;
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
|
|
key->eth.vlan_id =
|
|
rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]) {
|
|
mask->eth.vlan_id =
|
|
rocker_tlv_get_u16(flow_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID_MASK]);
|
|
}
|
|
|
|
switch (ntohs(key->eth.type)) {
|
|
case 0x0000:
|
|
mode = (key->eth.vlan_id) ? ACL_MODE_ANY_VLAN : ACL_MODE_ANY_TENANT;
|
|
break;
|
|
case 0x0800:
|
|
mode = (key->eth.vlan_id) ? ACL_MODE_IPV4_VLAN : ACL_MODE_IPV4_TENANT;
|
|
break;
|
|
case 0x86dd:
|
|
mode = (key->eth.vlan_id) ? ACL_MODE_IPV6_VLAN : ACL_MODE_IPV6_TENANT;
|
|
break;
|
|
default:
|
|
mode = (key->eth.vlan_id) ? ACL_MODE_NON_IP_VLAN :
|
|
ACL_MODE_NON_IP_TENANT;
|
|
break;
|
|
}
|
|
|
|
/* XXX only supporting VLAN modes for now */
|
|
if (mode != ACL_MODE_IPV4_VLAN &&
|
|
mode != ACL_MODE_IPV6_VLAN &&
|
|
mode != ACL_MODE_NON_IP_VLAN &&
|
|
mode != ACL_MODE_ANY_VLAN) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
switch (ntohs(key->eth.type)) {
|
|
case 0x0800:
|
|
case 0x86dd:
|
|
err = of_dpa_cmd_add_acl_ip(key, mask, flow_tlvs);
|
|
break;
|
|
}
|
|
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
|
|
action->write.group_id =
|
|
rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
|
|
}
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]) {
|
|
action->apply.copy_to_cpu =
|
|
rocker_tlv_get_u8(flow_tlvs[ROCKER_TLV_OF_DPA_COPY_CPU_ACTION]);
|
|
}
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static int of_dpa_cmd_flow_add_mod(OfDpa *of_dpa, OfDpaFlow *flow,
|
|
RockerTlv **flow_tlvs)
|
|
{
|
|
enum rocker_of_dpa_table_id tbl;
|
|
int err = ROCKER_OK;
|
|
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_TABLE_ID] ||
|
|
!flow_tlvs[ROCKER_TLV_OF_DPA_PRIORITY] ||
|
|
!flow_tlvs[ROCKER_TLV_OF_DPA_HARDTIME]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
tbl = rocker_tlv_get_le16(flow_tlvs[ROCKER_TLV_OF_DPA_TABLE_ID]);
|
|
flow->priority = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_PRIORITY]);
|
|
flow->hardtime = rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_HARDTIME]);
|
|
|
|
if (flow_tlvs[ROCKER_TLV_OF_DPA_IDLETIME]) {
|
|
if (tbl == ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT ||
|
|
tbl == ROCKER_OF_DPA_TABLE_ID_VLAN ||
|
|
tbl == ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
flow->idletime =
|
|
rocker_tlv_get_le32(flow_tlvs[ROCKER_TLV_OF_DPA_IDLETIME]);
|
|
}
|
|
|
|
switch (tbl) {
|
|
case ROCKER_OF_DPA_TABLE_ID_INGRESS_PORT:
|
|
err = of_dpa_cmd_add_ig_port(flow, flow_tlvs);
|
|
break;
|
|
case ROCKER_OF_DPA_TABLE_ID_VLAN:
|
|
err = of_dpa_cmd_add_vlan(flow, flow_tlvs);
|
|
break;
|
|
case ROCKER_OF_DPA_TABLE_ID_TERMINATION_MAC:
|
|
err = of_dpa_cmd_add_term_mac(flow, flow_tlvs);
|
|
break;
|
|
case ROCKER_OF_DPA_TABLE_ID_BRIDGING:
|
|
err = of_dpa_cmd_add_bridging(flow, flow_tlvs);
|
|
break;
|
|
case ROCKER_OF_DPA_TABLE_ID_UNICAST_ROUTING:
|
|
err = of_dpa_cmd_add_unicast_routing(flow, flow_tlvs);
|
|
break;
|
|
case ROCKER_OF_DPA_TABLE_ID_MULTICAST_ROUTING:
|
|
err = of_dpa_cmd_add_multicast_routing(flow, flow_tlvs);
|
|
break;
|
|
case ROCKER_OF_DPA_TABLE_ID_ACL_POLICY:
|
|
err = of_dpa_cmd_add_acl(flow, flow_tlvs);
|
|
break;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int of_dpa_cmd_flow_add(OfDpa *of_dpa, uint64_t cookie,
|
|
RockerTlv **flow_tlvs)
|
|
{
|
|
OfDpaFlow *flow = of_dpa_flow_find(of_dpa, cookie);
|
|
int err = ROCKER_OK;
|
|
|
|
if (flow) {
|
|
return -ROCKER_EEXIST;
|
|
}
|
|
|
|
flow = of_dpa_flow_alloc(cookie);
|
|
if (!flow) {
|
|
return -ROCKER_ENOMEM;
|
|
}
|
|
|
|
err = of_dpa_cmd_flow_add_mod(of_dpa, flow, flow_tlvs);
|
|
if (err) {
|
|
g_free(flow);
|
|
return err;
|
|
}
|
|
|
|
return of_dpa_flow_add(of_dpa, flow);
|
|
}
|
|
|
|
static int of_dpa_cmd_flow_mod(OfDpa *of_dpa, uint64_t cookie,
|
|
RockerTlv **flow_tlvs)
|
|
{
|
|
OfDpaFlow *flow = of_dpa_flow_find(of_dpa, cookie);
|
|
|
|
if (!flow) {
|
|
return -ROCKER_ENOENT;
|
|
}
|
|
|
|
return of_dpa_cmd_flow_add_mod(of_dpa, flow, flow_tlvs);
|
|
}
|
|
|
|
static int of_dpa_cmd_flow_del(OfDpa *of_dpa, uint64_t cookie)
|
|
{
|
|
OfDpaFlow *flow = of_dpa_flow_find(of_dpa, cookie);
|
|
|
|
if (!flow) {
|
|
return -ROCKER_ENOENT;
|
|
}
|
|
|
|
of_dpa_flow_del(of_dpa, flow);
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static int of_dpa_cmd_flow_get_stats(OfDpa *of_dpa, uint64_t cookie,
|
|
struct desc_info *info, char *buf)
|
|
{
|
|
OfDpaFlow *flow = of_dpa_flow_find(of_dpa, cookie);
|
|
size_t tlv_size;
|
|
int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) / 1000;
|
|
int pos;
|
|
|
|
if (!flow) {
|
|
return -ROCKER_ENOENT;
|
|
}
|
|
|
|
tlv_size = rocker_tlv_total_size(sizeof(uint32_t)) + /* duration */
|
|
rocker_tlv_total_size(sizeof(uint64_t)) + /* rx_pkts */
|
|
rocker_tlv_total_size(sizeof(uint64_t)); /* tx_ptks */
|
|
|
|
if (tlv_size > desc_buf_size(info)) {
|
|
return -ROCKER_EMSGSIZE;
|
|
}
|
|
|
|
pos = 0;
|
|
rocker_tlv_put_le32(buf, &pos, ROCKER_TLV_OF_DPA_FLOW_STAT_DURATION,
|
|
(int32_t)(now - flow->stats.install_time));
|
|
rocker_tlv_put_le64(buf, &pos, ROCKER_TLV_OF_DPA_FLOW_STAT_RX_PKTS,
|
|
flow->stats.rx_pkts);
|
|
rocker_tlv_put_le64(buf, &pos, ROCKER_TLV_OF_DPA_FLOW_STAT_TX_PKTS,
|
|
flow->stats.tx_pkts);
|
|
|
|
return desc_set_buf(info, tlv_size);
|
|
}
|
|
|
|
static int of_dpa_flow_cmd(OfDpa *of_dpa, struct desc_info *info,
|
|
char *buf, uint16_t cmd,
|
|
RockerTlv **flow_tlvs)
|
|
{
|
|
uint64_t cookie;
|
|
|
|
if (!flow_tlvs[ROCKER_TLV_OF_DPA_COOKIE]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
cookie = rocker_tlv_get_le64(flow_tlvs[ROCKER_TLV_OF_DPA_COOKIE]);
|
|
|
|
switch (cmd) {
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_ADD:
|
|
return of_dpa_cmd_flow_add(of_dpa, cookie, flow_tlvs);
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_MOD:
|
|
return of_dpa_cmd_flow_mod(of_dpa, cookie, flow_tlvs);
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_DEL:
|
|
return of_dpa_cmd_flow_del(of_dpa, cookie);
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_GET_STATS:
|
|
return of_dpa_cmd_flow_get_stats(of_dpa, cookie, info, buf);
|
|
}
|
|
|
|
return -ROCKER_ENOTSUP;
|
|
}
|
|
|
|
static int of_dpa_cmd_add_l2_interface(OfDpaGroup *group,
|
|
RockerTlv **group_tlvs)
|
|
{
|
|
if (!group_tlvs[ROCKER_TLV_OF_DPA_OUT_PPORT] ||
|
|
!group_tlvs[ROCKER_TLV_OF_DPA_POP_VLAN]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
group->l2_interface.out_pport =
|
|
rocker_tlv_get_le32(group_tlvs[ROCKER_TLV_OF_DPA_OUT_PPORT]);
|
|
group->l2_interface.pop_vlan =
|
|
rocker_tlv_get_u8(group_tlvs[ROCKER_TLV_OF_DPA_POP_VLAN]);
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static int of_dpa_cmd_add_l2_rewrite(OfDpa *of_dpa, OfDpaGroup *group,
|
|
RockerTlv **group_tlvs)
|
|
{
|
|
OfDpaGroup *l2_interface_group;
|
|
|
|
if (!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID_LOWER]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
group->l2_rewrite.group_id =
|
|
rocker_tlv_get_le32(group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID_LOWER]);
|
|
|
|
l2_interface_group = of_dpa_group_find(of_dpa, group->l2_rewrite.group_id);
|
|
if (!l2_interface_group ||
|
|
ROCKER_GROUP_TYPE_GET(l2_interface_group->id) !=
|
|
ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE) {
|
|
DPRINTF("l2 rewrite group needs a valid l2 interface group\n");
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
if (group_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]) {
|
|
memcpy(group->l2_rewrite.src_mac.a,
|
|
rocker_tlv_data(group_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]),
|
|
sizeof(group->l2_rewrite.src_mac.a));
|
|
}
|
|
|
|
if (group_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
|
|
memcpy(group->l2_rewrite.dst_mac.a,
|
|
rocker_tlv_data(group_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
|
|
sizeof(group->l2_rewrite.dst_mac.a));
|
|
}
|
|
|
|
if (group_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
|
|
group->l2_rewrite.vlan_id =
|
|
rocker_tlv_get_u16(group_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
|
|
if (ROCKER_GROUP_VLAN_GET(l2_interface_group->id) !=
|
|
(ntohs(group->l2_rewrite.vlan_id) & VLAN_VID_MASK)) {
|
|
DPRINTF("Set VLAN ID must be same as L2 interface group\n");
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
}
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static int of_dpa_cmd_add_l2_flood(OfDpa *of_dpa, OfDpaGroup *group,
|
|
RockerTlv **group_tlvs)
|
|
{
|
|
OfDpaGroup *l2_group;
|
|
RockerTlv **tlvs;
|
|
int err;
|
|
int i;
|
|
|
|
if (!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_COUNT] ||
|
|
!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_IDS]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
group->l2_flood.group_count =
|
|
rocker_tlv_get_le16(group_tlvs[ROCKER_TLV_OF_DPA_GROUP_COUNT]);
|
|
|
|
tlvs = g_malloc0((group->l2_flood.group_count + 1) *
|
|
sizeof(RockerTlv *));
|
|
if (!tlvs) {
|
|
return -ROCKER_ENOMEM;
|
|
}
|
|
|
|
g_free(group->l2_flood.group_ids);
|
|
group->l2_flood.group_ids =
|
|
g_malloc0(group->l2_flood.group_count * sizeof(uint32_t));
|
|
if (!group->l2_flood.group_ids) {
|
|
err = -ROCKER_ENOMEM;
|
|
goto err_out;
|
|
}
|
|
|
|
rocker_tlv_parse_nested(tlvs, group->l2_flood.group_count,
|
|
group_tlvs[ROCKER_TLV_OF_DPA_GROUP_IDS]);
|
|
|
|
for (i = 0; i < group->l2_flood.group_count; i++) {
|
|
group->l2_flood.group_ids[i] = rocker_tlv_get_le32(tlvs[i + 1]);
|
|
}
|
|
|
|
/* All of the L2 interface groups referenced by the L2 flood
|
|
* must have same VLAN
|
|
*/
|
|
|
|
for (i = 0; i < group->l2_flood.group_count; i++) {
|
|
l2_group = of_dpa_group_find(of_dpa, group->l2_flood.group_ids[i]);
|
|
if (!l2_group) {
|
|
continue;
|
|
}
|
|
if ((ROCKER_GROUP_TYPE_GET(l2_group->id) ==
|
|
ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE) &&
|
|
(ROCKER_GROUP_VLAN_GET(l2_group->id) !=
|
|
ROCKER_GROUP_VLAN_GET(group->id))) {
|
|
DPRINTF("l2 interface group 0x%08x VLAN doesn't match l2 "
|
|
"flood group 0x%08x\n",
|
|
group->l2_flood.group_ids[i], group->id);
|
|
err = -ROCKER_EINVAL;
|
|
goto err_out;
|
|
}
|
|
}
|
|
|
|
g_free(tlvs);
|
|
return ROCKER_OK;
|
|
|
|
err_out:
|
|
group->l2_flood.group_count = 0;
|
|
g_free(group->l2_flood.group_ids);
|
|
g_free(tlvs);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int of_dpa_cmd_add_l3_unicast(OfDpaGroup *group, RockerTlv **group_tlvs)
|
|
{
|
|
if (!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID_LOWER]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
group->l3_unicast.group_id =
|
|
rocker_tlv_get_le32(group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID_LOWER]);
|
|
|
|
if (group_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]) {
|
|
memcpy(group->l3_unicast.src_mac.a,
|
|
rocker_tlv_data(group_tlvs[ROCKER_TLV_OF_DPA_SRC_MAC]),
|
|
sizeof(group->l3_unicast.src_mac.a));
|
|
}
|
|
|
|
if (group_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]) {
|
|
memcpy(group->l3_unicast.dst_mac.a,
|
|
rocker_tlv_data(group_tlvs[ROCKER_TLV_OF_DPA_DST_MAC]),
|
|
sizeof(group->l3_unicast.dst_mac.a));
|
|
}
|
|
|
|
if (group_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]) {
|
|
group->l3_unicast.vlan_id =
|
|
rocker_tlv_get_u16(group_tlvs[ROCKER_TLV_OF_DPA_VLAN_ID]);
|
|
}
|
|
|
|
if (group_tlvs[ROCKER_TLV_OF_DPA_TTL_CHECK]) {
|
|
group->l3_unicast.ttl_check =
|
|
rocker_tlv_get_u8(group_tlvs[ROCKER_TLV_OF_DPA_TTL_CHECK]);
|
|
}
|
|
|
|
return ROCKER_OK;
|
|
}
|
|
|
|
static int of_dpa_cmd_group_do(OfDpa *of_dpa, uint32_t group_id,
|
|
OfDpaGroup *group, RockerTlv **group_tlvs)
|
|
{
|
|
uint8_t type = ROCKER_GROUP_TYPE_GET(group_id);
|
|
|
|
switch (type) {
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE:
|
|
return of_dpa_cmd_add_l2_interface(group, group_tlvs);
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE:
|
|
return of_dpa_cmd_add_l2_rewrite(of_dpa, group, group_tlvs);
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD:
|
|
/* Treat L2 multicast group same as a L2 flood group */
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST:
|
|
return of_dpa_cmd_add_l2_flood(of_dpa, group, group_tlvs);
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST:
|
|
return of_dpa_cmd_add_l3_unicast(group, group_tlvs);
|
|
}
|
|
|
|
return -ROCKER_ENOTSUP;
|
|
}
|
|
|
|
static int of_dpa_cmd_group_add(OfDpa *of_dpa, uint32_t group_id,
|
|
RockerTlv **group_tlvs)
|
|
{
|
|
OfDpaGroup *group = of_dpa_group_find(of_dpa, group_id);
|
|
int err;
|
|
|
|
if (group) {
|
|
return -ROCKER_EEXIST;
|
|
}
|
|
|
|
group = of_dpa_group_alloc(group_id);
|
|
if (!group) {
|
|
return -ROCKER_ENOMEM;
|
|
}
|
|
|
|
err = of_dpa_cmd_group_do(of_dpa, group_id, group, group_tlvs);
|
|
if (err) {
|
|
goto err_cmd_add;
|
|
}
|
|
|
|
err = of_dpa_group_add(of_dpa, group);
|
|
if (err) {
|
|
goto err_cmd_add;
|
|
}
|
|
|
|
return ROCKER_OK;
|
|
|
|
err_cmd_add:
|
|
g_free(group);
|
|
return err;
|
|
}
|
|
|
|
static int of_dpa_cmd_group_mod(OfDpa *of_dpa, uint32_t group_id,
|
|
RockerTlv **group_tlvs)
|
|
{
|
|
OfDpaGroup *group = of_dpa_group_find(of_dpa, group_id);
|
|
|
|
if (!group) {
|
|
return -ROCKER_ENOENT;
|
|
}
|
|
|
|
return of_dpa_cmd_group_do(of_dpa, group_id, group, group_tlvs);
|
|
}
|
|
|
|
static int of_dpa_cmd_group_del(OfDpa *of_dpa, uint32_t group_id)
|
|
{
|
|
OfDpaGroup *group = of_dpa_group_find(of_dpa, group_id);
|
|
|
|
if (!group) {
|
|
return -ROCKER_ENOENT;
|
|
}
|
|
|
|
return of_dpa_group_del(of_dpa, group);
|
|
}
|
|
|
|
static int of_dpa_cmd_group_get_stats(OfDpa *of_dpa, uint32_t group_id,
|
|
struct desc_info *info, char *buf)
|
|
{
|
|
return -ROCKER_ENOTSUP;
|
|
}
|
|
|
|
static int of_dpa_group_cmd(OfDpa *of_dpa, struct desc_info *info,
|
|
char *buf, uint16_t cmd, RockerTlv **group_tlvs)
|
|
{
|
|
uint32_t group_id;
|
|
|
|
if (!group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]) {
|
|
return -ROCKER_EINVAL;
|
|
}
|
|
|
|
group_id = rocker_tlv_get_le32(group_tlvs[ROCKER_TLV_OF_DPA_GROUP_ID]);
|
|
|
|
switch (cmd) {
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_ADD:
|
|
return of_dpa_cmd_group_add(of_dpa, group_id, group_tlvs);
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_MOD:
|
|
return of_dpa_cmd_group_mod(of_dpa, group_id, group_tlvs);
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_DEL:
|
|
return of_dpa_cmd_group_del(of_dpa, group_id);
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_GET_STATS:
|
|
return of_dpa_cmd_group_get_stats(of_dpa, group_id, info, buf);
|
|
}
|
|
|
|
return -ROCKER_ENOTSUP;
|
|
}
|
|
|
|
static int of_dpa_cmd(World *world, struct desc_info *info,
|
|
char *buf, uint16_t cmd, RockerTlv *cmd_info_tlv)
|
|
{
|
|
OfDpa *of_dpa = world_private(world);
|
|
RockerTlv *tlvs[ROCKER_TLV_OF_DPA_MAX + 1];
|
|
|
|
rocker_tlv_parse_nested(tlvs, ROCKER_TLV_OF_DPA_MAX, cmd_info_tlv);
|
|
|
|
switch (cmd) {
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_ADD:
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_MOD:
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_DEL:
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_FLOW_GET_STATS:
|
|
return of_dpa_flow_cmd(of_dpa, info, buf, cmd, tlvs);
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_ADD:
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_MOD:
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_DEL:
|
|
case ROCKER_TLV_CMD_TYPE_OF_DPA_GROUP_GET_STATS:
|
|
return of_dpa_group_cmd(of_dpa, info, buf, cmd, tlvs);
|
|
}
|
|
|
|
return -ROCKER_ENOTSUP;
|
|
}
|
|
|
|
static gboolean rocker_int64_equal(gconstpointer v1, gconstpointer v2)
|
|
{
|
|
return *((const uint64_t *)v1) == *((const uint64_t *)v2);
|
|
}
|
|
|
|
static guint rocker_int64_hash(gconstpointer v)
|
|
{
|
|
return (guint)*(const uint64_t *)v;
|
|
}
|
|
|
|
static int of_dpa_init(World *world)
|
|
{
|
|
OfDpa *of_dpa = world_private(world);
|
|
|
|
of_dpa->world = world;
|
|
|
|
of_dpa->flow_tbl = g_hash_table_new_full(rocker_int64_hash,
|
|
rocker_int64_equal,
|
|
NULL, g_free);
|
|
if (!of_dpa->flow_tbl) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
of_dpa->group_tbl = g_hash_table_new_full(g_int_hash, g_int_equal,
|
|
NULL, g_free);
|
|
if (!of_dpa->group_tbl) {
|
|
goto err_group_tbl;
|
|
}
|
|
|
|
/* XXX hardcode some artificial table max values */
|
|
of_dpa->flow_tbl_max_size = 100;
|
|
of_dpa->group_tbl_max_size = 100;
|
|
|
|
return 0;
|
|
|
|
err_group_tbl:
|
|
g_hash_table_destroy(of_dpa->flow_tbl);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void of_dpa_uninit(World *world)
|
|
{
|
|
OfDpa *of_dpa = world_private(world);
|
|
|
|
g_hash_table_destroy(of_dpa->group_tbl);
|
|
g_hash_table_destroy(of_dpa->flow_tbl);
|
|
}
|
|
|
|
struct of_dpa_flow_fill_context {
|
|
RockerOfDpaFlowList *list;
|
|
uint32_t tbl_id;
|
|
};
|
|
|
|
static void of_dpa_flow_fill(void *cookie, void *value, void *user_data)
|
|
{
|
|
struct of_dpa_flow *flow = value;
|
|
struct of_dpa_flow_key *key = &flow->key;
|
|
struct of_dpa_flow_key *mask = &flow->mask;
|
|
struct of_dpa_flow_fill_context *flow_context = user_data;
|
|
RockerOfDpaFlowList *new;
|
|
RockerOfDpaFlow *nflow;
|
|
RockerOfDpaFlowKey *nkey;
|
|
RockerOfDpaFlowMask *nmask;
|
|
RockerOfDpaFlowAction *naction;
|
|
|
|
if (flow_context->tbl_id != -1 &&
|
|
flow_context->tbl_id != key->tbl_id) {
|
|
return;
|
|
}
|
|
|
|
new = g_malloc0(sizeof(*new));
|
|
nflow = new->value = g_malloc0(sizeof(*nflow));
|
|
nkey = nflow->key = g_malloc0(sizeof(*nkey));
|
|
nmask = nflow->mask = g_malloc0(sizeof(*nmask));
|
|
naction = nflow->action = g_malloc0(sizeof(*naction));
|
|
|
|
nflow->cookie = flow->cookie;
|
|
nflow->hits = flow->stats.hits;
|
|
nkey->priority = flow->priority;
|
|
nkey->tbl_id = key->tbl_id;
|
|
|
|
if (key->in_pport || mask->in_pport) {
|
|
nkey->has_in_pport = true;
|
|
nkey->in_pport = key->in_pport;
|
|
}
|
|
|
|
if (nkey->has_in_pport && mask->in_pport != 0xffffffff) {
|
|
nmask->has_in_pport = true;
|
|
nmask->in_pport = mask->in_pport;
|
|
}
|
|
|
|
if (key->eth.vlan_id || mask->eth.vlan_id) {
|
|
nkey->has_vlan_id = true;
|
|
nkey->vlan_id = ntohs(key->eth.vlan_id);
|
|
}
|
|
|
|
if (nkey->has_vlan_id && mask->eth.vlan_id != 0xffff) {
|
|
nmask->has_vlan_id = true;
|
|
nmask->vlan_id = ntohs(mask->eth.vlan_id);
|
|
}
|
|
|
|
if (key->tunnel_id || mask->tunnel_id) {
|
|
nkey->has_tunnel_id = true;
|
|
nkey->tunnel_id = key->tunnel_id;
|
|
}
|
|
|
|
if (nkey->has_tunnel_id && mask->tunnel_id != 0xffffffff) {
|
|
nmask->has_tunnel_id = true;
|
|
nmask->tunnel_id = mask->tunnel_id;
|
|
}
|
|
|
|
if (memcmp(key->eth.src.a, zero_mac.a, ETH_ALEN) ||
|
|
memcmp(mask->eth.src.a, zero_mac.a, ETH_ALEN)) {
|
|
nkey->has_eth_src = true;
|
|
nkey->eth_src = qemu_mac_strdup_printf(key->eth.src.a);
|
|
}
|
|
|
|
if (nkey->has_eth_src && memcmp(mask->eth.src.a, ff_mac.a, ETH_ALEN)) {
|
|
nmask->has_eth_src = true;
|
|
nmask->eth_src = qemu_mac_strdup_printf(mask->eth.src.a);
|
|
}
|
|
|
|
if (memcmp(key->eth.dst.a, zero_mac.a, ETH_ALEN) ||
|
|
memcmp(mask->eth.dst.a, zero_mac.a, ETH_ALEN)) {
|
|
nkey->has_eth_dst = true;
|
|
nkey->eth_dst = qemu_mac_strdup_printf(key->eth.dst.a);
|
|
}
|
|
|
|
if (nkey->has_eth_dst && memcmp(mask->eth.dst.a, ff_mac.a, ETH_ALEN)) {
|
|
nmask->has_eth_dst = true;
|
|
nmask->eth_dst = qemu_mac_strdup_printf(mask->eth.dst.a);
|
|
}
|
|
|
|
if (key->eth.type) {
|
|
|
|
nkey->has_eth_type = true;
|
|
nkey->eth_type = ntohs(key->eth.type);
|
|
|
|
switch (ntohs(key->eth.type)) {
|
|
case 0x0800:
|
|
case 0x86dd:
|
|
if (key->ip.proto || mask->ip.proto) {
|
|
nkey->has_ip_proto = true;
|
|
nkey->ip_proto = key->ip.proto;
|
|
}
|
|
if (nkey->has_ip_proto && mask->ip.proto != 0xff) {
|
|
nmask->has_ip_proto = true;
|
|
nmask->ip_proto = mask->ip.proto;
|
|
}
|
|
if (key->ip.tos || mask->ip.tos) {
|
|
nkey->has_ip_tos = true;
|
|
nkey->ip_tos = key->ip.tos;
|
|
}
|
|
if (nkey->has_ip_tos && mask->ip.tos != 0xff) {
|
|
nmask->has_ip_tos = true;
|
|
nmask->ip_tos = mask->ip.tos;
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch (ntohs(key->eth.type)) {
|
|
case 0x0800:
|
|
if (key->ipv4.addr.dst || mask->ipv4.addr.dst) {
|
|
char *dst = inet_ntoa(*(struct in_addr *)&key->ipv4.addr.dst);
|
|
int dst_len = of_dpa_mask2prefix(mask->ipv4.addr.dst);
|
|
nkey->has_ip_dst = true;
|
|
nkey->ip_dst = g_strdup_printf("%s/%d", dst, dst_len);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (flow->action.goto_tbl) {
|
|
naction->has_goto_tbl = true;
|
|
naction->goto_tbl = flow->action.goto_tbl;
|
|
}
|
|
|
|
if (flow->action.write.group_id) {
|
|
naction->has_group_id = true;
|
|
naction->group_id = flow->action.write.group_id;
|
|
}
|
|
|
|
if (flow->action.apply.new_vlan_id) {
|
|
naction->has_new_vlan_id = true;
|
|
naction->new_vlan_id = flow->action.apply.new_vlan_id;
|
|
}
|
|
|
|
new->next = flow_context->list;
|
|
flow_context->list = new;
|
|
}
|
|
|
|
RockerOfDpaFlowList *qmp_query_rocker_of_dpa_flows(const char *name,
|
|
bool has_tbl_id,
|
|
uint32_t tbl_id,
|
|
Error **errp)
|
|
{
|
|
struct rocker *r;
|
|
struct world *w;
|
|
struct of_dpa *of_dpa;
|
|
struct of_dpa_flow_fill_context fill_context = {
|
|
.list = NULL,
|
|
.tbl_id = tbl_id,
|
|
};
|
|
|
|
r = rocker_find(name);
|
|
if (!r) {
|
|
error_set(errp, ERROR_CLASS_GENERIC_ERROR,
|
|
"rocker %s not found", name);
|
|
return NULL;
|
|
}
|
|
|
|
w = rocker_get_world(r, ROCKER_WORLD_TYPE_OF_DPA);
|
|
if (!w) {
|
|
error_set(errp, ERROR_CLASS_GENERIC_ERROR,
|
|
"rocker %s doesn't have OF-DPA world", name);
|
|
return NULL;
|
|
}
|
|
|
|
of_dpa = world_private(w);
|
|
|
|
g_hash_table_foreach(of_dpa->flow_tbl, of_dpa_flow_fill, &fill_context);
|
|
|
|
return fill_context.list;
|
|
}
|
|
|
|
struct of_dpa_group_fill_context {
|
|
RockerOfDpaGroupList *list;
|
|
uint8_t type;
|
|
};
|
|
|
|
static void of_dpa_group_fill(void *key, void *value, void *user_data)
|
|
{
|
|
struct of_dpa_group *group = value;
|
|
struct of_dpa_group_fill_context *flow_context = user_data;
|
|
RockerOfDpaGroupList *new;
|
|
RockerOfDpaGroup *ngroup;
|
|
struct uint32List *id;
|
|
int i;
|
|
|
|
if (flow_context->type != 9 &&
|
|
flow_context->type != ROCKER_GROUP_TYPE_GET(group->id)) {
|
|
return;
|
|
}
|
|
|
|
new = g_malloc0(sizeof(*new));
|
|
ngroup = new->value = g_malloc0(sizeof(*ngroup));
|
|
|
|
ngroup->id = group->id;
|
|
|
|
ngroup->type = ROCKER_GROUP_TYPE_GET(group->id);
|
|
|
|
switch (ngroup->type) {
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_INTERFACE:
|
|
ngroup->has_vlan_id = true;
|
|
ngroup->vlan_id = ROCKER_GROUP_VLAN_GET(group->id);
|
|
ngroup->has_pport = true;
|
|
ngroup->pport = ROCKER_GROUP_PORT_GET(group->id);
|
|
ngroup->has_out_pport = true;
|
|
ngroup->out_pport = group->l2_interface.out_pport;
|
|
ngroup->has_pop_vlan = true;
|
|
ngroup->pop_vlan = group->l2_interface.pop_vlan;
|
|
break;
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_REWRITE:
|
|
ngroup->has_index = true;
|
|
ngroup->index = ROCKER_GROUP_INDEX_LONG_GET(group->id);
|
|
ngroup->has_group_id = true;
|
|
ngroup->group_id = group->l2_rewrite.group_id;
|
|
if (group->l2_rewrite.vlan_id) {
|
|
ngroup->has_set_vlan_id = true;
|
|
ngroup->set_vlan_id = ntohs(group->l2_rewrite.vlan_id);
|
|
}
|
|
if (memcmp(group->l2_rewrite.src_mac.a, zero_mac.a, ETH_ALEN)) {
|
|
ngroup->has_set_eth_src = true;
|
|
ngroup->set_eth_src =
|
|
qemu_mac_strdup_printf(group->l2_rewrite.src_mac.a);
|
|
}
|
|
if (memcmp(group->l2_rewrite.dst_mac.a, zero_mac.a, ETH_ALEN)) {
|
|
ngroup->has_set_eth_dst = true;
|
|
ngroup->set_eth_dst =
|
|
qemu_mac_strdup_printf(group->l2_rewrite.dst_mac.a);
|
|
}
|
|
break;
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_FLOOD:
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L2_MCAST:
|
|
ngroup->has_vlan_id = true;
|
|
ngroup->vlan_id = ROCKER_GROUP_VLAN_GET(group->id);
|
|
ngroup->has_index = true;
|
|
ngroup->index = ROCKER_GROUP_INDEX_GET(group->id);
|
|
for (i = 0; i < group->l2_flood.group_count; i++) {
|
|
ngroup->has_group_ids = true;
|
|
id = g_malloc0(sizeof(*id));
|
|
id->value = group->l2_flood.group_ids[i];
|
|
id->next = ngroup->group_ids;
|
|
ngroup->group_ids = id;
|
|
}
|
|
break;
|
|
case ROCKER_OF_DPA_GROUP_TYPE_L3_UCAST:
|
|
ngroup->has_index = true;
|
|
ngroup->index = ROCKER_GROUP_INDEX_LONG_GET(group->id);
|
|
ngroup->has_group_id = true;
|
|
ngroup->group_id = group->l3_unicast.group_id;
|
|
if (group->l3_unicast.vlan_id) {
|
|
ngroup->has_set_vlan_id = true;
|
|
ngroup->set_vlan_id = ntohs(group->l3_unicast.vlan_id);
|
|
}
|
|
if (memcmp(group->l3_unicast.src_mac.a, zero_mac.a, ETH_ALEN)) {
|
|
ngroup->has_set_eth_src = true;
|
|
ngroup->set_eth_src =
|
|
qemu_mac_strdup_printf(group->l3_unicast.src_mac.a);
|
|
}
|
|
if (memcmp(group->l3_unicast.dst_mac.a, zero_mac.a, ETH_ALEN)) {
|
|
ngroup->has_set_eth_dst = true;
|
|
ngroup->set_eth_dst =
|
|
qemu_mac_strdup_printf(group->l3_unicast.dst_mac.a);
|
|
}
|
|
if (group->l3_unicast.ttl_check) {
|
|
ngroup->has_ttl_check = true;
|
|
ngroup->ttl_check = group->l3_unicast.ttl_check;
|
|
}
|
|
break;
|
|
}
|
|
|
|
new->next = flow_context->list;
|
|
flow_context->list = new;
|
|
}
|
|
|
|
RockerOfDpaGroupList *qmp_query_rocker_of_dpa_groups(const char *name,
|
|
bool has_type,
|
|
uint8_t type,
|
|
Error **errp)
|
|
{
|
|
struct rocker *r;
|
|
struct world *w;
|
|
struct of_dpa *of_dpa;
|
|
struct of_dpa_group_fill_context fill_context = {
|
|
.list = NULL,
|
|
.type = type,
|
|
};
|
|
|
|
r = rocker_find(name);
|
|
if (!r) {
|
|
error_set(errp, ERROR_CLASS_GENERIC_ERROR,
|
|
"rocker %s not found", name);
|
|
return NULL;
|
|
}
|
|
|
|
w = rocker_get_world(r, ROCKER_WORLD_TYPE_OF_DPA);
|
|
if (!w) {
|
|
error_set(errp, ERROR_CLASS_GENERIC_ERROR,
|
|
"rocker %s doesn't have OF-DPA world", name);
|
|
return NULL;
|
|
}
|
|
|
|
of_dpa = world_private(w);
|
|
|
|
g_hash_table_foreach(of_dpa->group_tbl, of_dpa_group_fill, &fill_context);
|
|
|
|
return fill_context.list;
|
|
}
|
|
|
|
static WorldOps of_dpa_ops = {
|
|
.init = of_dpa_init,
|
|
.uninit = of_dpa_uninit,
|
|
.ig = of_dpa_ig,
|
|
.cmd = of_dpa_cmd,
|
|
};
|
|
|
|
World *of_dpa_world_alloc(Rocker *r)
|
|
{
|
|
return world_alloc(r, sizeof(OfDpa), ROCKER_WORLD_TYPE_OF_DPA, &of_dpa_ops);
|
|
}
|