diff options
author | Ben Pfaff <blp@nicira.com> | 2015-05-05 17:16:41 -0700 |
---|---|---|
committer | Ben Pfaff <blp@nicira.com> | 2015-05-05 17:21:05 -0700 |
commit | 678729a2ce532aec98e7837315f88125977d569d (patch) | |
tree | 1690180e4ac2bb76f1513a14563d2e606d701d15 | |
parent | 3b7cb7e166b0c63cf05bceef36c7fc7eb64f292c (diff) | |
download | openvswitch-678729a2ce532aec98e7837315f88125977d569d.tar.gz |
ovn-controller: Implement translation of OVN flows into OpenFlow.
Signed-off-by: Ben Pfaff <blp@nicira.com>
Acked-by: Justin Pettit <jpettit@nicira.com>
-rw-r--r-- | ovn/controller/automake.mk | 4 | ||||
-rw-r--r-- | ovn/controller/ovn-controller.c | 5 | ||||
-rw-r--r-- | ovn/controller/pipeline.c | 374 | ||||
-rw-r--r-- | ovn/controller/pipeline.h | 30 |
4 files changed, 411 insertions, 2 deletions
diff --git a/ovn/controller/automake.mk b/ovn/controller/automake.mk index 4a266daa3..51c73be2d 100644 --- a/ovn/controller/automake.mk +++ b/ovn/controller/automake.mk @@ -5,7 +5,9 @@ ovn_controller_ovn_controller_SOURCES = \ ovn/controller/chassis.c \ ovn/controller/chassis.h \ ovn/controller/ovn-controller.c \ - ovn/controller/ovn-controller.h + ovn/controller/ovn-controller.h \ + ovn/controller/pipeline.c \ + ovn/controller/pipeline.h ovn_controller_ovn_controller_LDADD = ovn/lib/libovn.la lib/libopenvswitch.la man_MANS += ovn/controller/ovn-controller.8 EXTRA_DIST += ovn/controller/ovn-controller.8.xml diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c index 9d61a48a2..5f5f677ab 100644 --- a/ovn/controller/ovn-controller.c +++ b/ovn/controller/ovn-controller.c @@ -40,6 +40,7 @@ #include "ovn-controller.h" #include "bindings.h" #include "chassis.h" +#include "pipeline.h" VLOG_DEFINE_THIS_MODULE(main); @@ -179,6 +180,7 @@ main(int argc, char *argv[]) chassis_init(&ctx); bindings_init(&ctx); + pipeline_init(); get_initial_snapshot(ctx.ovs_idl); @@ -186,7 +188,6 @@ main(int argc, char *argv[]) ctx.ovnsb_idl = ovsdb_idl_create(ovnsb_remote, &sbrec_idl_class, true, true); - get_initial_snapshot(ctx.ovnsb_idl); exiting = false; @@ -222,6 +223,7 @@ main(int argc, char *argv[]) chassis_run(&ctx); bindings_run(&ctx); + pipeline_run(&ctx); unixctl_server_run(unixctl); unixctl_server_wait(unixctl); @@ -235,6 +237,7 @@ main(int argc, char *argv[]) } unixctl_server_destroy(unixctl); + pipeline_destroy(&ctx); bindings_destroy(&ctx); chassis_destroy(&ctx); diff --git a/ovn/controller/pipeline.c b/ovn/controller/pipeline.c new file mode 100644 index 000000000..f3343c354 --- /dev/null +++ b/ovn/controller/pipeline.c @@ -0,0 +1,374 @@ +/* Copyright (c) 2015 Nicira, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> +#include "pipeline.h" +#include "dynamic-string.h" +#include "ofp-actions.h" +#include "ofpbuf.h" +#include "openvswitch/vlog.h" +#include "ovn/controller/ovn-controller.h" +#include "ovn/lib/actions.h" +#include "ovn/lib/expr.h" +#include "ovn/lib/ovn-sb-idl.h" +#include "simap.h" + +VLOG_DEFINE_THIS_MODULE(pipeline); + +/* Symbol table. */ + +/* Contains "struct expr_symbol"s for fields supported by OVN pipeline. */ +static struct shash symtab; + +static void +symtab_init(void) +{ + shash_init(&symtab); + + /* Reserve a pair of registers for the logical inport and outport. A full + * 32-bit register each is bigger than we need, but the expression code + * doesn't yet support string fields that occupy less than a full OXM. */ + expr_symtab_add_string(&symtab, "inport", MFF_LOG_INPORT, NULL); + expr_symtab_add_string(&symtab, "outport", MFF_LOG_OUTPORT, NULL); + + /* Registers. We omit the registers that would otherwise overlap the + * reserved fields. */ + for (enum mf_field_id id = MFF_REG0; id < MFF_REG0 + FLOW_N_REGS; id++) { + if (id != MFF_LOG_INPORT && id != MFF_LOG_OUTPORT) { + char name[8]; + + snprintf(name, sizeof name, "reg%d", id - MFF_REG0); + expr_symtab_add_field(&symtab, name, id, NULL, false); + } + } + + /* Data fields. */ + expr_symtab_add_field(&symtab, "eth.src", MFF_ETH_SRC, NULL, false); + expr_symtab_add_field(&symtab, "eth.dst", MFF_ETH_DST, NULL, false); + expr_symtab_add_field(&symtab, "eth.type", MFF_ETH_TYPE, NULL, true); + + expr_symtab_add_field(&symtab, "vlan.tci", MFF_VLAN_TCI, NULL, false); + expr_symtab_add_predicate(&symtab, "vlan.present", "vlan.tci[12]"); + expr_symtab_add_subfield(&symtab, "vlan.pcp", "vlan.present", + "vlan.tci[13..15]"); + expr_symtab_add_subfield(&symtab, "vlan.vid", "vlan.present", + "vlan.tci[0..11]"); + + expr_symtab_add_predicate(&symtab, "ip4", "eth.type == 0x800"); + expr_symtab_add_predicate(&symtab, "ip6", "eth.type == 0x86dd"); + expr_symtab_add_predicate(&symtab, "ip", "ip4 || ip6"); + expr_symtab_add_field(&symtab, "ip.proto", MFF_IP_PROTO, "ip", true); + expr_symtab_add_field(&symtab, "ip.dscp", MFF_IP_DSCP, "ip", false); + expr_symtab_add_field(&symtab, "ip.ecn", MFF_IP_ECN, "ip", false); + expr_symtab_add_field(&symtab, "ip.ttl", MFF_IP_TTL, "ip", false); + + expr_symtab_add_field(&symtab, "ip4.src", MFF_IPV4_SRC, "ip4", false); + expr_symtab_add_field(&symtab, "ip4.dst", MFF_IPV4_DST, "ip4", false); + + expr_symtab_add_predicate(&symtab, "icmp4", "ip4 && ip.proto == 1"); + expr_symtab_add_field(&symtab, "icmp4.type", MFF_ICMPV4_TYPE, "icmp4", + false); + expr_symtab_add_field(&symtab, "icmp4.code", MFF_ICMPV4_CODE, "icmp4", + false); + + expr_symtab_add_field(&symtab, "ip6.src", MFF_IPV6_SRC, "ip6", false); + expr_symtab_add_field(&symtab, "ip6.dst", MFF_IPV6_DST, "ip6", false); + expr_symtab_add_field(&symtab, "ip6.label", MFF_IPV6_LABEL, "ip6", false); + + expr_symtab_add_predicate(&symtab, "icmp6", "ip6 && ip.proto == 58"); + expr_symtab_add_field(&symtab, "icmp6.type", MFF_ICMPV6_TYPE, "icmp6", + true); + expr_symtab_add_field(&symtab, "icmp6.code", MFF_ICMPV6_CODE, "icmp6", + true); + + expr_symtab_add_predicate(&symtab, "icmp", "icmp4 || icmp6"); + + expr_symtab_add_field(&symtab, "ip.frag", MFF_IP_FRAG, "ip", false); + expr_symtab_add_predicate(&symtab, "ip.is_frag", "ip.frag[0]"); + expr_symtab_add_predicate(&symtab, "ip.later_frag", "ip.frag[1]"); + expr_symtab_add_predicate(&symtab, "ip.first_frag", + "ip.is_frag && !ip.later_frag"); + + expr_symtab_add_predicate(&symtab, "arp", "eth.type == 0x806"); + expr_symtab_add_field(&symtab, "arp.op", MFF_ARP_OP, "arp", false); + expr_symtab_add_field(&symtab, "arp.spa", MFF_ARP_SPA, "arp", false); + expr_symtab_add_field(&symtab, "arp.sha", MFF_ARP_SHA, "arp", false); + expr_symtab_add_field(&symtab, "arp.tpa", MFF_ARP_TPA, "arp", false); + expr_symtab_add_field(&symtab, "arp.tha", MFF_ARP_THA, "arp", false); + + expr_symtab_add_predicate(&symtab, "nd", + "icmp6.type == {135, 136} && icmp6.code == 0"); + expr_symtab_add_field(&symtab, "nd.target", MFF_ND_TARGET, "nd", false); + expr_symtab_add_field(&symtab, "nd.sll", MFF_ND_SLL, + "nd && icmp6.type == 135", false); + expr_symtab_add_field(&symtab, "nd.tll", MFF_ND_TLL, + "nd && icmp6.type == 136", false); + + expr_symtab_add_predicate(&symtab, "tcp", "ip.proto == 6"); + expr_symtab_add_field(&symtab, "tcp.src", MFF_TCP_SRC, "tcp", false); + expr_symtab_add_field(&symtab, "tcp.dst", MFF_TCP_DST, "tcp", false); + expr_symtab_add_field(&symtab, "tcp.flags", MFF_TCP_FLAGS, "tcp", false); + + expr_symtab_add_predicate(&symtab, "udp", "ip.proto == 17"); + expr_symtab_add_field(&symtab, "udp.src", MFF_UDP_SRC, "udp", false); + expr_symtab_add_field(&symtab, "udp.dst", MFF_UDP_DST, "udp", false); + + expr_symtab_add_predicate(&symtab, "sctp", "ip.proto == 132"); + expr_symtab_add_field(&symtab, "sctp.src", MFF_SCTP_SRC, "sctp", false); + expr_symtab_add_field(&symtab, "sctp.dst", MFF_SCTP_DST, "sctp", false); +} + +/* Logical datapaths and logical port numbers. */ + +/* A logical datapath. + * + * 'uuid' is the UUID that represents the logical datapath in the OVN_SB + * database. + * + * 'integer' represents the logical datapath as an integer value that is unique + * only within the local hypervisor. Because of its size, this value is more + * practical for use in an OpenFlow flow table than a UUID. + * + * 'ports' maps 'logical_port' names to 'tunnel_key' values in the OVN_SB + * Bindings table within the logical datapath. */ +struct logical_datapath { + struct hmap_node hmap_node; /* Indexed on 'uuid'. */ + struct uuid uuid; /* The logical_datapath's UUID. */ + uint32_t integer; /* Locally unique among logical datapaths. */ + struct simap ports; /* Logical port name to port number. */ +}; + +/* Contains "struct logical_datapath"s. */ +static struct hmap logical_datapaths = HMAP_INITIALIZER(&logical_datapaths); + +/* Finds and returns the logical_datapath with the given 'uuid', or NULL if + * no such logical_datapath exists. */ +static struct logical_datapath * +ldp_lookup(const struct uuid *uuid) +{ + struct logical_datapath *ldp; + HMAP_FOR_EACH_IN_BUCKET (ldp, hmap_node, uuid_hash(uuid), + &logical_datapaths) { + if (uuid_equals(&ldp->uuid, uuid)) { + return ldp; + } + } + return NULL; +} + +/* Creates a new logical_datapath with the given 'uuid'. */ +static struct logical_datapath * +ldp_create(const struct uuid *uuid) +{ + static uint32_t next_integer = 1; + struct logical_datapath *ldp; + + /* We don't handle the case where the logical datapaths wrap around. */ + ovs_assert(next_integer); + + ldp = xmalloc(sizeof *ldp); + hmap_insert(&logical_datapaths, &ldp->hmap_node, uuid_hash(uuid)); + ldp->uuid = *uuid; + ldp->integer = next_integer++; + simap_init(&ldp->ports); + return ldp; +} + +static void +ldp_free(struct logical_datapath *ldp) +{ + simap_destroy(&ldp->ports); + hmap_remove(&logical_datapaths, &ldp->hmap_node); + free(ldp); +} + +/* Iterates through all of the records in the Bindings table, updating the + * table of logical_datapaths to match the values found in active Bindings. */ +static void +ldp_run(struct controller_ctx *ctx) +{ + struct logical_datapath *ldp; + HMAP_FOR_EACH (ldp, hmap_node, &logical_datapaths) { + simap_clear(&ldp->ports); + } + + const struct sbrec_bindings *binding; + SBREC_BINDINGS_FOR_EACH (binding, ctx->ovnsb_idl) { + struct logical_datapath *ldp; + + ldp = ldp_lookup(&binding->logical_datapath); + if (!ldp) { + ldp = ldp_create(&binding->logical_datapath); + } + + simap_put(&ldp->ports, binding->logical_port, binding->tunnel_key); + } + + struct logical_datapath *next_ldp; + HMAP_FOR_EACH_SAFE (ldp, next_ldp, hmap_node, &logical_datapaths) { + if (simap_is_empty(&ldp->ports)) { + ldp_free(ldp); + } + } +} + +static void +ldp_destroy(void) +{ + struct logical_datapath *ldp, *next_ldp; + HMAP_FOR_EACH_SAFE (ldp, next_ldp, hmap_node, &logical_datapaths) { + ldp_free(ldp); + } +} + +void +pipeline_init(void) +{ + symtab_init(); +} + +static void +add_ovn_flow(uint8_t table_id, uint16_t priority, const struct match *match, + const struct ofpbuf *ofpacts) +{ + struct ds s = DS_EMPTY_INITIALIZER; + ds_put_format(&s, "table_id=%"PRIu8", ", table_id); + ds_put_format(&s, "priority=%"PRIu16", ", priority); + match_format(match, &s, OFP_DEFAULT_PRIORITY); + ds_put_cstr(&s, ", actions="); + ofpacts_format(ofpacts->data, ofpacts->size, &s); + VLOG_INFO("%s", ds_cstr(&s)); + ds_destroy(&s); +} + +/* Translates logical flows in the Pipeline table in the OVN_SB database + * into OpenFlow flows. + * + * We put the Pipeline flows into OpenFlow tables 16 through 47 (inclusive). */ +void +pipeline_run(struct controller_ctx *ctx) +{ + struct hmap flows = HMAP_INITIALIZER(&flows); + uint32_t conj_id_ofs = 1; + + ldp_run(ctx); + + VLOG_INFO("starting run..."); + const struct sbrec_pipeline *pipeline; + SBREC_PIPELINE_FOR_EACH (pipeline, ctx->ovnsb_idl) { + /* Find the "struct logical_datapath" asssociated with this Pipeline + * row. If there's no such struct, that must be because no logical + * ports are bound to that logical datapath, so there's no point in + * maintaining any flows for it anyway, so skip it. */ + const struct logical_datapath *ldp; + ldp = ldp_lookup(&pipeline->logical_datapath); + if (!ldp) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_INFO_RL(&rl, + "logical flow for unknown logical datapath "UUID_FMT, + UUID_ARGS(&pipeline->logical_datapath)); + continue; + } + + /* Translate OVN actions into OpenFlow actions. */ + uint64_t ofpacts_stub[64 / 8]; + struct ofpbuf ofpacts; + struct expr *prereqs; + uint8_t next_table_id; + char *error; + + ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub); + next_table_id = pipeline->table_id < 31 ? pipeline->table_id + 17 : 0; + error = actions_parse_string(pipeline->actions, &symtab, &ldp->ports, + next_table_id, &ofpacts, &prereqs); + if (error) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "error parsing actions \"%s\": %s", + pipeline->actions, error); + free(error); + continue; + } + + /* Translate OVN match into table of OpenFlow matches. */ + struct hmap matches; + struct expr *expr; + + expr = expr_parse_string(pipeline->match, &symtab, &error); + if (!error) { + if (prereqs) { + expr = expr_combine(EXPR_T_AND, expr, prereqs); + prereqs = NULL; + } + expr = expr_annotate(expr, &symtab, &error); + } + if (error) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "error parsing match \"%s\": %s", + pipeline->match, error); + expr_destroy(prereqs); + ofpbuf_uninit(&ofpacts); + free(error); + continue; + } + + expr = expr_simplify(expr); + expr = expr_normalize(expr); + uint32_t n_conjs = expr_to_matches(expr, &ldp->ports, &matches); + expr_destroy(expr); + + /* Prepare the OpenFlow matches for adding to the flow table. */ + struct expr_match *m; + HMAP_FOR_EACH (m, hmap_node, &matches) { + match_set_metadata(&m->match, htonll(ldp->integer)); + if (m->match.wc.masks.conj_id) { + m->match.flow.conj_id += conj_id_ofs; + } + if (!m->n) { + add_ovn_flow(pipeline->table_id + 16, pipeline->priority, + &m->match, &ofpacts); + } else { + uint64_t conj_stubs[64 / 8]; + struct ofpbuf conj; + + ofpbuf_use_stub(&conj, conj_stubs, sizeof conj_stubs); + for (int i = 0; i < m->n; i++) { + const struct cls_conjunction *src = &m->conjunctions[i]; + struct ofpact_conjunction *dst; + + dst = ofpact_put_CONJUNCTION(&conj); + dst->id = src->id + conj_id_ofs; + dst->clause = src->clause; + dst->n_clauses = src->n_clauses; + } + add_ovn_flow(pipeline->table_id + 16, pipeline->priority, + &m->match, &conj); + ofpbuf_uninit(&conj); + } + } + + /* Clean up. */ + expr_matches_destroy(&matches); + ofpbuf_uninit(&ofpacts); + conj_id_ofs += n_conjs; + } + VLOG_INFO("...done"); +} + +void +pipeline_destroy(struct controller_ctx *ctx OVS_UNUSED) +{ + expr_symtab_destroy(&symtab); + ldp_destroy(); +} diff --git a/ovn/controller/pipeline.h b/ovn/controller/pipeline.h new file mode 100644 index 000000000..e7eb2f315 --- /dev/null +++ b/ovn/controller/pipeline.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2015 Nicira, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef OVN_PIPELINE_H +#define OVN_PIPELINE_H 1 + +struct controller_ctx; + +/* Logical ports. */ +#define MFF_LOG_INPORT MFF_REG6 /* Logical input port. */ +#define MFF_LOG_OUTPORT MFF_REG7 /* Logical output port. */ + +void pipeline_init(void); +void pipeline_run(struct controller_ctx *); +void pipeline_destroy(struct controller_ctx *); + +#endif /* ovn/pipeline.h */ |