diff options
Diffstat (limited to 'vswitchd/mgmt.c')
-rw-r--r-- | vswitchd/mgmt.c | 679 |
1 files changed, 679 insertions, 0 deletions
diff --git a/vswitchd/mgmt.c b/vswitchd/mgmt.c new file mode 100644 index 000000000..f5dcd1840 --- /dev/null +++ b/vswitchd/mgmt.c @@ -0,0 +1,679 @@ +/* Copyright (c) 2009 Nicira Networks + * + * 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 3 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * In addition, as a special exception, Nicira Networks gives permission + * to link the code of its release of vswitchd with the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the same + * license as the "OpenSSL" library), and distribute the linked + * executables. You must obey the GNU General Public License in all + * respects for all of the code used other than "OpenSSL". If you modify + * this file, you may extend this exception to your version of the file, + * but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. + * + */ + +#include <config.h> + +#include <arpa/inet.h> +#include <assert.h> +#include <errno.h> +#include <stdlib.h> + +#include "bridge.h" +#include "cfg.h" +#include "coverage.h" +#include "list.h" +#include "mgmt.h" +#include "openflow/nicira-ext.h" +#include "openflow/openflow.h" +#include "openflow/openflow-mgmt.h" +#include "ofpbuf.h" +#include "ovs-vswitchd.h" +#include "packets.h" +#include "rconn.h" +#include "svec.h" +#include "vconn.h" +#include "vconn-ssl.h" +#include "xtoxll.h" + +#define THIS_MODULE VLM_mgmt +#include "vlog.h" + +#define MAX_BACKOFF_DEFAULT 15 +#define INACTIVITY_PROBE_DEFAULT 15 + +static struct svec mgmt_cfg; +static uint8_t cfg_cookie[CFG_COOKIE_LEN]; +static struct rconn *mgmt_rconn; +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60); +static struct svec capabilities; +uint64_t mgmt_id; + + +#define TXQ_LIMIT 128 /* Max number of packets to queue for tx. */ +struct rconn_packet_counter *txqlen; /* # pkts queued for tx on mgmt_rconn. */ + +static uint64_t pick_fallback_mgmt_id(void); +static void send_config_update(uint32_t xid, bool use_xid); +static void send_resources_update(uint32_t xid, bool use_xid); + +void +mgmt_init(void) +{ + txqlen = rconn_packet_counter_create(); + + svec_init(&mgmt_cfg); + svec_init(&capabilities); + svec_add_nocopy(&capabilities, + xasprintf("com.nicira.mgmt.manager=true\n")); + + mgmt_id = cfg_get_dpid(0, "mgmt.id"); + if (!mgmt_id) { + /* Randomly generate a mgmt id */ + mgmt_id = pick_fallback_mgmt_id(); + } +} + +#ifdef HAVE_OPENSSL +static bool +config_string_change(const char *key, char **valuep) +{ + const char *value = cfg_get_string(0, "%s", key); + if (value && (!*valuep || strcmp(value, *valuep))) { + free(*valuep); + *valuep = xstrdup(value); + return true; + } else { + return false; + } +} + +static void +mgmt_configure_ssl(void) +{ + static char *private_key_file; + static char *certificate_file; + static char *cacert_file; + + /* XXX SSL should be configurable separate from the bridges. + * XXX should be possible to de-configure SSL. */ + if (config_string_change("ssl.private-key", &private_key_file)) { + vconn_ssl_set_private_key_file(private_key_file); + } + + if (config_string_change("ssl.certificate", &certificate_file)) { + vconn_ssl_set_certificate_file(certificate_file); + } + + if (config_string_change("ssl.ca-cert", &cacert_file)) { + vconn_ssl_set_ca_cert_file(cacert_file, + cfg_get_bool(0, "ssl.bootstrap-ca-cert")); + } +} +#endif + +void +mgmt_reconfigure(void) +{ + struct svec new_cfg; + uint8_t new_cookie[CFG_COOKIE_LEN]; + bool cfg_updated = false; + const char *controller_name; + int max_backoff; + int inactivity_probe; + int retval; + + if (!cfg_has_section("mgmt")) { + if (mgmt_rconn) { + rconn_destroy(mgmt_rconn); + mgmt_rconn = NULL; + } + return; + } + + /* If this is an established connection, send a resources update. */ + /* xxx This is wasteful if there were no resource changes!!! */ + if (mgmt_rconn) { + send_resources_update(0, false); + } + + cfg_get_cookie(new_cookie); + if (memcmp(cfg_cookie, new_cookie, sizeof(cfg_cookie))) { + memcpy(cfg_cookie, new_cookie, sizeof(cfg_cookie)); + cfg_updated = true; + } + + svec_init(&new_cfg); + cfg_get_section(&new_cfg, "mgmt"); + if (svec_equal(&mgmt_cfg, &new_cfg)) { + /* Reconnecting to the controller causes the config file to be + * resent automatically. If we're not reconnecting and the + * config file has changed, we need to notify the controller of + * changes. */ + if (cfg_updated && mgmt_rconn) { + send_config_update(0, false); + } + svec_destroy(&new_cfg); + return; + } + + controller_name = cfg_get_string(0, "mgmt.controller"); + if (!controller_name) { + VLOG_ERR("no controller specified for managment"); + svec_destroy(&new_cfg); + return; + } + + max_backoff = cfg_get_int(0, "mgmt.max-backoff"); + if (max_backoff < 1) { + max_backoff = MAX_BACKOFF_DEFAULT; + } else if (max_backoff > 3600) { + max_backoff = 3600; + } + + inactivity_probe = cfg_get_int(0, "mgmt.inactivity-probe"); + if (inactivity_probe < 5) { + inactivity_probe = INACTIVITY_PROBE_DEFAULT; + } + + /* xxx If this changes, we need to restart bridges to use new id, + * xxx but they need the id before the connect to controller, but we + * xxx need their dpids. */ + /* Check if a different mgmt id has been assigned. */ + if (cfg_has("mgmt.id")) { + uint64_t cfg_mgmt_id = cfg_get_dpid(0, "mgmt.id"); + if (cfg_mgmt_id != mgmt_id) { + mgmt_id = cfg_mgmt_id; + } + } + + svec_swap(&new_cfg, &mgmt_cfg); + svec_destroy(&new_cfg); + +#ifdef HAVE_OPENSSL + /* Configure SSL. */ + mgmt_configure_ssl(); +#endif + + if (mgmt_rconn) { + rconn_destroy(mgmt_rconn); + mgmt_rconn = NULL; + } + mgmt_rconn = rconn_create(inactivity_probe, max_backoff); + retval = rconn_connect(mgmt_rconn, controller_name); + if (retval == EAFNOSUPPORT) { + VLOG_ERR("no support for %s vconn", controller_name); + } +} + +static int +send_openflow_buffer(struct ofpbuf *buffer) +{ + int retval; + + if (!mgmt_rconn) { + VLOG_ERR("attempt to send openflow packet with no rconn\n"); + return EINVAL; + } + + update_openflow_length(buffer); + retval = rconn_send_with_limit(mgmt_rconn, buffer, txqlen, TXQ_LIMIT); + if (retval) { + VLOG_WARN_RL(&rl, "send to %s failed: %s", + rconn_get_name(mgmt_rconn), strerror(retval)); + } + return retval; +} + +static void +send_features_reply(uint32_t xid) +{ + struct ofpbuf *buffer; + struct ofp_switch_features *ofr; + + ofr = make_openflow_xid(sizeof *ofr, OFPT_FEATURES_REPLY, xid, &buffer); + ofr->datapath_id = 0; + ofr->n_tables = 0; + ofr->n_buffers = 0; + ofr->capabilities = 0; + ofr->actions = 0; + send_openflow_buffer(buffer); +} + +static void * +make_ofmp_xid(size_t ofmp_len, uint16_t type, uint32_t xid, + struct ofpbuf **bufferp) +{ + struct ofmp_header *oh; + + oh = make_openflow_xid(ofmp_len, OFPT_VENDOR, xid, bufferp); + oh->header.vendor = htonl(NX_VENDOR_ID); + oh->header.subtype = htonl(NXT_MGMT); + oh->type = htons(type); + + return oh; +} + +static void * +make_ofmp(size_t ofmp_len, uint16_t type, struct ofpbuf **bufferp) +{ + struct ofmp_header *oh; + + oh = make_openflow(ofmp_len, OFPT_VENDOR, bufferp); + oh->header.vendor = htonl(NX_VENDOR_ID); + oh->header.subtype = htonl(NXT_MGMT); + oh->type = htons(type); + + return oh; +} + +static void +send_capability_reply(uint32_t xid) +{ + int i; + struct ofpbuf *buffer; + struct ofmp_capability_reply *ofmpcr; + + ofmpcr = make_ofmp_xid(sizeof *ofmpcr, OFMPT_CAPABILITY_REPLY, + xid, &buffer); + ofmpcr->format = htonl(OFMPCOF_SIMPLE); + ofmpcr->mgmt_id = htonll(mgmt_id); + for (i=0; i<capabilities.n; i++) { + ofpbuf_put(buffer, capabilities.names[i], + strlen(capabilities.names[i])); + } + send_openflow_buffer(buffer); +} + +static void +send_resources_update(uint32_t xid, bool use_xid) +{ + struct ofpbuf *buffer; + struct ofmp_resources_update *ofmpru; + struct ofmp_tlv *tlv; + struct svec br_list; + int i; + + if (use_xid) { + ofmpru = make_ofmp_xid(sizeof *ofmpru, OFMPT_RESOURCES_UPDATE, + xid, &buffer); + } else { + ofmpru = make_ofmp(sizeof *ofmpru, OFMPT_RESOURCES_UPDATE, &buffer); + } + + svec_init(&br_list); + cfg_get_subsections(&br_list, "bridge"); + for (i=0; i < br_list.n; i++) { + struct ofmptsr_dp *dp_tlv; + uint64_t dp_id = bridge_get_datapathid(br_list.names[i]); + if (!dp_id) { + VLOG_WARN_RL(&rl, "bridge %s doesn't seem to exist", + br_list.names[i]); + continue; + } + dp_tlv = ofpbuf_put_zeros(buffer, sizeof(*dp_tlv)); + dp_tlv->type = htons(OFMPTSR_DP); + dp_tlv->len = htons(sizeof(*dp_tlv)); + + dp_tlv->dp_id = htonll(dp_id); + memcpy(dp_tlv->name, br_list.names[i], strlen(br_list.names[i])+1); + } + + /* Put end marker. */ + tlv = ofpbuf_put_zeros(buffer, sizeof(*tlv)); + tlv->type = htons(OFMPTSR_END); + tlv->len = htons(sizeof(*tlv)); + send_openflow_buffer(buffer); +} + +static void +send_config_update(uint32_t xid, bool use_xid) +{ + struct ofpbuf *buffer; + struct ofmp_config_update *ofmpcu; + + if (use_xid) { + ofmpcu = make_ofmp_xid(sizeof *ofmpcu, OFMPT_CONFIG_UPDATE, + xid, &buffer); + } else { + ofmpcu = make_ofmp(sizeof *ofmpcu, OFMPT_CONFIG_UPDATE, &buffer); + } + + ofmpcu->format = htonl(OFMPCOF_SIMPLE); + memcpy(ofmpcu->cookie, cfg_cookie, sizeof(ofmpcu->cookie)); + cfg_buf_put(buffer); + send_openflow_buffer(buffer); +} + +static void +send_config_update_ack(uint32_t xid, bool success) +{ + struct ofpbuf *buffer; + struct ofmp_config_update_ack *ofmpcua; + + ofmpcua = make_ofmp_xid(sizeof *ofmpcua, OFMPT_CONFIG_UPDATE_ACK, + xid, &buffer); + + ofmpcua->format = htonl(OFMPCOF_SIMPLE); + if (success) { + ofmpcua->flags = htonl(OFMPCUAF_SUCCESS); + } + cfg_get_cookie(ofmpcua->cookie); + send_openflow_buffer(buffer); +} + +static void +send_ofmp_error_msg(uint32_t xid, uint16_t type, uint16_t code, + const void *data, size_t len) +{ + struct ofpbuf *buffer; + struct ofmp_error_msg *oem; + + oem = make_ofmp_xid(sizeof(*oem)+len, OFMPT_ERROR, xid, &buffer); + oem->type = htons(type); + oem->code = htons(code); + memcpy(oem->data, data, len); + send_openflow_buffer(buffer); +} + +static void +send_error_msg(uint32_t xid, uint16_t type, uint16_t code, + const void *data, size_t len) +{ + struct ofpbuf *buffer; + struct ofp_error_msg *oem; + + oem = make_openflow_xid(sizeof(*oem)+len, OFPT_ERROR, xid, &buffer); + oem->type = htons(type); + oem->code = htons(code); + memcpy(oem->data, data, len); + send_openflow_buffer(buffer); +} + +static int +recv_echo_request(uint32_t xid UNUSED, const void *msg) +{ + const struct ofp_header *rq = msg; + send_openflow_buffer(make_echo_reply(rq)); + return 0; +} + +static int +recv_features_request(uint32_t xid, const void *msg UNUSED) +{ + send_features_reply(xid); + return 0; +} + +static int +recv_set_config(uint32_t xid UNUSED, const void *msg UNUSED) +{ + /* Nothing to configure! */ + return 0; +} + +static int +recv_ofmp_capability_request(uint32_t xid, const struct ofmp_header *ofmph) +{ + struct ofmp_capability_request *ofmpcr; + + if (htons(ofmph->header.header.length) != sizeof(*ofmpcr)) { + /* xxx Send error */ + return -EINVAL; + } + + ofmpcr = (struct ofmp_capability_request *)ofmph; + if (ofmpcr->format != htonl(OFMPCAF_SIMPLE)) { + /* xxx Send error */ + return -EINVAL; + } + + send_capability_reply(xid); + + return 0; +} + +static int +recv_ofmp_resources_request(uint32_t xid, const void *msg UNUSED) +{ + send_resources_update(xid, true); + return 0; +} + +static int +recv_ofmp_config_request(uint32_t xid, const struct ofmp_header *ofmph) +{ + struct ofmp_config_request *ofmpcr; + + if (htons(ofmph->header.header.length) != sizeof(*ofmpcr)) { + /* xxx Send error */ + return -EINVAL; + } + + ofmpcr = (struct ofmp_config_request *)ofmph; + if (ofmpcr->format != htonl(OFMPCOF_SIMPLE)) { + /* xxx Send error */ + return -EINVAL; + } + + send_config_update(xid, true); + + return 0; +} + +static int +recv_ofmp_config_update(uint32_t xid, const struct ofmp_header *ofmph) +{ + struct ofmp_config_update *ofmpcu; + int data_len; + + data_len = htons(ofmph->header.header.length) - sizeof(*ofmpcu); + if (data_len <= sizeof(*ofmpcu)) { + /* xxx Send error. */ + return -EINVAL; + } + + ofmpcu = (struct ofmp_config_update *)ofmph; + if (ofmpcu->format != htonl(OFMPCOF_SIMPLE)) { + /* xxx Send error */ + return -EINVAL; + } + + /* Check if the supplied cookie matches our current understanding of + * it. If they don't match, tell the controller and let it sort + * things out. */ + if (cfg_lock(ofmpcu->cookie, 0)) { + /* xxx cfg_lock can fail for other reasons, such as being + * xxx locked... */ + VLOG_WARN_RL(&rl, "config update failed due to bad cookie\n"); + send_config_update_ack(xid, false); + return 0; + } + + /* xxx We should probably do more sanity checking than this. */ + + cfg_write_data(ofmpcu->data, data_len); + cfg_unlock(); + + /* Send the ACK before running reconfigure, since our management + * connection settings may have changed. */ + send_config_update_ack(xid, true); + + reconfigure(); + + + return 0; +} + +static +int recv_ofmp(uint32_t xid, struct ofmp_header *ofmph) +{ + /* xxx Should sanity-check for min/max length */ + switch (ntohs(ofmph->type)) + { + case OFMPT_CAPABILITY_REQUEST: + return recv_ofmp_capability_request(xid, ofmph); + case OFMPT_RESOURCES_REQUEST: + return recv_ofmp_resources_request(xid, ofmph); + case OFMPT_CONFIG_REQUEST: + return recv_ofmp_config_request(xid, ofmph); + case OFMPT_CONFIG_UPDATE: + return recv_ofmp_config_update(xid, ofmph); + default: + VLOG_WARN_RL(&rl, "unknown mgmt message: %d", + ntohs(ofmph->type)); + return -EINVAL; + } +} + +static int +recv_nx_msg(uint32_t xid, const void *oh) +{ + const struct nicira_header *nh = oh; + + switch (ntohl(nh->subtype)) { + + case NXT_MGMT: + return recv_ofmp(xid, (struct ofmp_header *)oh); + + default: + send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE, + oh, htons(nh->header.length)); + return -EINVAL; + } +} + +static int +recv_vendor(uint32_t xid, const void *oh) +{ + const struct ofp_vendor_header *ovh = oh; + + switch (ntohl(ovh->vendor)) + { + case NX_VENDOR_ID: + return recv_nx_msg(xid, oh); + + default: + VLOG_WARN_RL(&rl, "unknown vendor: 0x%x", ntohl(ovh->vendor)); + send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_VENDOR, + oh, ntohs(ovh->header.length)); + return -EINVAL; + } +} + +static int +handle_msg(uint32_t xid, const void *msg, size_t length) +{ + int (*handler)(uint32_t, const void *); + struct ofp_header *oh; + size_t min_size; + + COVERAGE_INC(mgmt_received); + + /* Check encapsulated length. */ + oh = (struct ofp_header *) msg; + if (ntohs(oh->length) > length) { + return -EINVAL; + } + assert(oh->version == OFP_VERSION); + + /* Figure out how to handle it. */ + switch (oh->type) { + case OFPT_ECHO_REQUEST: + min_size = sizeof(struct ofp_header); + handler = recv_echo_request; + break; + case OFPT_ECHO_REPLY: + return 0; + case OFPT_FEATURES_REQUEST: + min_size = sizeof(struct ofp_header); + handler = recv_features_request; + break; + case OFPT_SET_CONFIG: + min_size = sizeof(struct ofp_switch_config); + handler = recv_set_config; + break; + case OFPT_VENDOR: + min_size = sizeof(struct ofp_vendor_header); + handler = recv_vendor; + break; + default: + VLOG_WARN_RL(&rl, "unknown openflow type: %d", oh->type); + send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_TYPE, + msg, length); + return -EINVAL; + } + + /* Handle it. */ + if (length < min_size) { + return -EFAULT; + } + return handler(xid, msg); +} + +void +mgmt_run(void) +{ + int i; + + if (!mgmt_rconn) { + return; + } + + rconn_run(mgmt_rconn); + + /* Do some processing, but cap it at a reasonable amount so that + * other processing doesn't starve. */ + for (i=0; i<50; i++) { + struct ofpbuf *buffer; + struct ofp_header *oh; + + buffer = rconn_recv(mgmt_rconn); + if (!buffer) { + break; + } + + if (buffer->size >= sizeof *oh) { + oh = buffer->data; + handle_msg(oh->xid, buffer->data, buffer->size); + ofpbuf_delete(buffer); + } else { + VLOG_WARN_RL(&rl, "received too-short OpenFlow message"); + } + } +} + +void +mgmt_wait(void) +{ + if (!mgmt_rconn) { + return; + } + + rconn_run_wait(mgmt_rconn); + rconn_recv_wait(mgmt_rconn); +} + +static uint64_t +pick_fallback_mgmt_id(void) +{ + uint8_t ea[ETH_ADDR_LEN]; + eth_addr_random(ea); + ea[0] = 0x00; /* Set Nicira OUI. */ + ea[1] = 0x23; + ea[2] = 0x20; + return eth_addr_to_uint64(ea); +} |