/* * QEMU rocker switch emulation - OF-DPA flow processing support * * Copyright (c) 2014 Scott Feldman * * 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) { 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); } 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); }