/* 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.
+ * 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:
*
- * 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.
+ * 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 <assert.h>
#include <errno.h>
#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/types.h>
#include "bridge.h"
#include "cfg.h"
#include "svec.h"
#include "vconn.h"
#include "vconn-ssl.h"
+#include "xenserver.h"
#include "xtoxll.h"
#define THIS_MODULE VLM_mgmt
static struct svec mgmt_cfg;
static uint8_t cfg_cookie[CFG_COOKIE_LEN];
+static bool need_reconfigure = false;
static struct rconn *mgmt_rconn;
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60);
static struct svec capabilities;
+static struct ofpbuf ext_data_buffer;
+static uint32_t ext_data_xid = UINT32_MAX;
uint64_t mgmt_id;
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);
+static int recv_ofmp(uint32_t xid, struct ofmp_header *ofmph, size_t len);
void
mgmt_init(void)
/* Randomly generate a mgmt id */
mgmt_id = pick_fallback_mgmt_id();
}
+
+ ofpbuf_init(&ext_data_buffer, 0);
}
#ifdef HAVE_OPENSSL
static char *private_key_file;
static char *certificate_file;
static char *cacert_file;
+ struct stat s;
/* XXX SSL should be configurable separate from the bridges.
* XXX should be possible to de-configure SSL. */
vconn_ssl_set_certificate_file(certificate_file);
}
- if (config_string_change("ssl.ca-cert", &cacert_file)) {
+ /* We assume that even if the filename hasn't changed, if the CA cert
+ * file has been removed, that we want to move back into
+ * boot-strapping mode. This opens a small security hole, because
+ * the old certificate will still be trusted until vSwitch is
+ * restarted. We may want to address this in vconn's SSL library. */
+ if (config_string_change("ssl.ca-cert", &cacert_file)
+ || (stat(cacert_file, &s) && errno == ENOENT)) {
vconn_ssl_set_ca_cert_file(cacert_file,
cfg_get_bool(0, "ssl.bootstrap-ca-cert"));
}
int retval;
if (!cfg_has_section("mgmt")) {
+ svec_clear(&mgmt_cfg);
if (mgmt_rconn) {
rconn_destroy(mgmt_rconn);
mgmt_rconn = NULL;
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);
+ /* Reset the extended message buffer when we create a new
+ * management connection. */
+ ofpbuf_clear(&ext_data_buffer);
}
static void *
return oh;
}
+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;
+ }
+
+ /* Make sure there's room to transmit the data. We don't want to
+ * fail part way through a send. */
+ if (rconn_packet_counter_read(txqlen) >= TXQ_LIMIT) {
+ return EAGAIN;
+ }
+
+ /* OpenFlow messages use a 16-bit length field, so messages over 64K
+ * must be broken into multiple pieces.
+ */
+ if (buffer->size <= 65535) {
+ update_openflow_length(buffer);
+ retval = rconn_send(mgmt_rconn, buffer, txqlen);
+ if (retval) {
+ VLOG_WARN_RL(&rl, "send to %s failed: %s",
+ rconn_get_name(mgmt_rconn), strerror(retval));
+ }
+ return retval;
+ } else {
+ struct ofmp_header *header = (struct ofmp_header *)buffer->data;
+ uint32_t xid = header->header.header.xid;
+ size_t remain = buffer->size;
+ uint8_t *ptr = buffer->data;
+
+ /* Mark the OpenFlow header with a zero length to indicate some
+ * funkiness.
+ */
+ header->header.header.length = 0;
+
+ while (remain > 0) {
+ struct ofpbuf *new_buffer;
+ struct ofmp_extended_data *oed;
+ size_t new_len = MIN(65535 - sizeof *oed, remain);
+
+ oed = make_ofmp_xid(sizeof *oed, OFMPT_EXTENDED_DATA, xid,
+ &new_buffer);
+ oed->type = header->type;
+
+ if (remain > new_len) {
+ oed->flags |= OFMPEDF_MORE_DATA;
+ }
+
+ /* Copy the entire original message, including the OpenFlow
+ * header, since management protocol structure definitions
+ * include these headers.
+ */
+ ofpbuf_put(new_buffer, ptr, new_len);
+
+ update_openflow_length(new_buffer);
+ retval = rconn_send(mgmt_rconn, new_buffer, txqlen);
+ if (retval) {
+ VLOG_WARN_RL(&rl, "send to %s failed: %s",
+ rconn_get_name(mgmt_rconn), strerror(retval));
+ ofpbuf_delete(buffer);
+ return retval;
+ }
+
+ remain -= new_len;
+ ptr += new_len;
+ }
+
+ ofpbuf_delete(buffer);
+ return 0;
+ }
+}
+
+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
send_capability_reply(uint32_t xid)
{
struct ofmp_resources_update *ofmpru;
struct ofmp_tlv *tlv;
struct svec br_list;
+ struct svec port_list;
+ const char *host_uuid;
int i;
if (use_xid) {
ofmpru = make_ofmp(sizeof *ofmpru, OFMPT_RESOURCES_UPDATE, &buffer);
}
+ /* On XenServer systems, each host has its own UUID, which we provide
+ * to the controller.
+ */
+ host_uuid = xenserver_get_host_uuid();
+ if (host_uuid) {
+ struct ofmptsr_mgmt_uuid *mgmt_uuid_tlv;
+
+ mgmt_uuid_tlv = ofpbuf_put_zeros(buffer, sizeof(*mgmt_uuid_tlv));
+ mgmt_uuid_tlv->type = htons(OFMPTSR_MGMT_UUID);
+ mgmt_uuid_tlv->len = htons(sizeof(*mgmt_uuid_tlv));
+ mgmt_uuid_tlv->mgmt_id = htonll(mgmt_id);
+ memcpy(mgmt_uuid_tlv->uuid, host_uuid, OFMP_UUID_LEN);
+ }
+
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]);
+ uint64_t dp_id;
+ int n_uuid;
+
+ 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]);
dp_tlv->dp_id = htonll(dp_id);
memcpy(dp_tlv->name, br_list.names[i], strlen(br_list.names[i])+1);
+
+ /* On XenServer systems, each network has one or more UUIDs
+ * associated with it, which we provide to the controller.
+ */
+ n_uuid = cfg_count("bridge.%s.xs-network-uuids", br_list.names[i]);
+ if (n_uuid) {
+ struct ofmptsr_dp_uuid *dp_uuid_tlv;
+ size_t tlv_len = sizeof(*dp_uuid_tlv) + n_uuid * OFMP_UUID_LEN;
+ int j;
+
+ dp_uuid_tlv = ofpbuf_put_zeros(buffer, sizeof(*dp_uuid_tlv));
+ dp_uuid_tlv->type = htons(OFMPTSR_DP_UUID);
+ dp_uuid_tlv->len = htons(tlv_len);
+ dp_uuid_tlv->dp_id = htonll(dp_id);
+
+ for (j=0; j<n_uuid; j++) {
+ const char *dp_uuid = cfg_get_string(j,
+ "bridge.%s.xs-network-uuids", br_list.names[i]);
+
+ /* The UUID list could change underneath us, so just
+ * fill with zeros in that case. Another update will be
+ * initiated shortly, which should contain corrected data.
+ */
+ if (dp_uuid) {
+ ofpbuf_put(buffer, dp_uuid, OFMP_UUID_LEN);
+ } else {
+ ofpbuf_put_zeros(buffer, OFMP_UUID_LEN);
+ }
+ }
+ }
}
+ svec_destroy(&br_list);
+
+ /* On XenServer systems, extended information about virtual interfaces
+ * (VIFs) is available, which is needed by the controller.
+ */
+ svec_init(&port_list);
+ bridge_get_ifaces(&port_list);
+ for (i=0; i < port_list.n; i++) {
+ const char *vif_uuid, *vm_uuid, *net_uuid;
+ uint64_t vif_mac;
+ struct ofmptsr_vif *vif_tlv;
+
+ vif_uuid = cfg_get_string(0, "port.%s.vif-uuid", port_list.names[i]);
+ if (!vif_uuid) {
+ continue;
+ }
+
+ vif_tlv = ofpbuf_put_zeros(buffer, sizeof(*vif_tlv));
+ vif_tlv->type = htons(OFMPTSR_VIF);
+ vif_tlv->len = htons(sizeof(*vif_tlv));
+
+ memcpy(vif_tlv->name, port_list.names[i], strlen(port_list.names[i])+1);
+ memcpy(vif_tlv->vif_uuid, vif_uuid, sizeof(vif_tlv->vif_uuid));
+
+ vm_uuid = cfg_get_string(0, "port.%s.vm-uuid", port_list.names[i]);
+ if (vm_uuid) {
+ memcpy(vif_tlv->vm_uuid, vm_uuid, sizeof(vif_tlv->vm_uuid));
+ } else {
+ /* In case the vif disappeared underneath us. */
+ memset(vif_tlv->vm_uuid, '\0', sizeof(vif_tlv->vm_uuid));
+ }
+
+ net_uuid = cfg_get_string(0, "port.%s.net-uuid", port_list.names[i]);
+ if (net_uuid) {
+ memcpy(vif_tlv->net_uuid, net_uuid, sizeof(vif_tlv->net_uuid));
+ } else {
+ /* In case the vif disappeared underneath us. */
+ memset(vif_tlv->net_uuid, '\0', sizeof(vif_tlv->net_uuid));
+ }
+
+ vif_mac = cfg_get_mac(0, "port.%s.vif-mac", port_list.names[i]);
+ vif_tlv->vif_mac = htonll(vif_mac);
+ }
+ svec_destroy(&port_list);
/* Put end marker. */
tlv = ofpbuf_put_zeros(buffer, sizeof(*tlv));
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)
}
static int
-recv_ofmp_capability_request(uint32_t xid, const struct ofmp_header *ofmph)
+recv_ofmp_capability_request(uint32_t xid, const struct ofmp_header *ofmph,
+ size_t len)
{
struct ofmp_capability_request *ofmpcr;
- if (htons(ofmph->header.header.length) != sizeof(*ofmpcr)) {
+ if (len != sizeof(*ofmpcr)) {
/* xxx Send error */
return -EINVAL;
}
}
static int
-recv_ofmp_resources_request(uint32_t xid, const void *msg UNUSED)
+recv_ofmp_resources_request(uint32_t xid, const void *msg UNUSED,
+ size_t len UNUSED)
{
send_resources_update(xid, true);
return 0;
}
static int
-recv_ofmp_config_request(uint32_t xid, const struct ofmp_header *ofmph)
+recv_ofmp_config_request(uint32_t xid, const struct ofmp_header *ofmph,
+ size_t len)
{
struct ofmp_config_request *ofmpcr;
- if (htons(ofmph->header.header.length) != sizeof(*ofmpcr)) {
+ if (len != sizeof(*ofmpcr)) {
/* xxx Send error */
return -EINVAL;
}
}
static int
-recv_ofmp_config_update(uint32_t xid, const struct ofmp_header *ofmph)
+recv_ofmp_config_update(uint32_t xid, const struct ofmp_header *ofmph,
+ size_t len)
{
struct ofmp_config_update *ofmpcu;
int data_len;
- data_len = htons(ofmph->header.header.length) - sizeof(*ofmpcu);
+ data_len = len - sizeof(*ofmpcu);
if (data_len <= sizeof(*ofmpcu)) {
/* xxx Send error. */
return -EINVAL;
/* 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");
+
+ /* Check if our local view matches the controller, in which
+ * case, it is likely that there were local modifications
+ * without our being told to reread the config file. */
+ if (!memcmp(cfg_cookie, ofmpcu->cookie, sizeof cfg_cookie)) {
+ VLOG_WARN_RL(&rl, "config appears to have been locally modified "
+ "without having told ovs-vswitchd to reload");
+ }
send_config_update_ack(xid, false);
return 0;
}
* connection settings may have changed. */
send_config_update_ack(xid, true);
- reconfigure();
+ need_reconfigure = true;
+
+ return 0;
+}
+
+static int
+recv_ofmp_extended_data(uint32_t xid, const struct ofmp_header *ofmph,
+ size_t len)
+{
+ int data_len;
+ struct ofmp_extended_data *ofmped;
+
+ if (len <= sizeof(*ofmped)) {
+ /* xxx Send error. */
+ return -EINVAL;
+ }
+
+ ext_data_xid = xid;
+ ofmped = (struct ofmp_extended_data *)ofmph;
+
+ data_len = len - sizeof(*ofmped);
+ ofpbuf_put(&ext_data_buffer, ofmped->data, data_len);
+ if (!(ofmped->flags & OFMPEDF_MORE_DATA)) {
+ struct ofmp_header *new_oh;
+ int error;
+
+ /* An embedded message must be greater than the size of an
+ * OpenFlow message. */
+ new_oh = ofpbuf_at(&ext_data_buffer, 0, 65536);
+ if (!new_oh) {
+ VLOG_WARN_RL(&rl, "received short embedded message: %d\n",
+ ext_data_buffer.size);
+ return -EINVAL;
+ }
+
+ /* Make sure that this is a management message and that there's
+ * not an embedded extended data message. */
+ if ((new_oh->header.vendor != htonl(NX_VENDOR_ID))
+ || (new_oh->header.subtype != htonl(NXT_MGMT))
+ || (new_oh->type == htonl(OFMPT_EXTENDED_DATA))) {
+ VLOG_WARN_RL(&rl, "received bad embedded message\n");
+ return -EINVAL;
+ }
+ new_oh->header.header.xid = ext_data_xid;
+ new_oh->header.header.length = 0;
+
+ error = recv_ofmp(xid, ext_data_buffer.data, ext_data_buffer.size);
+ ofpbuf_clear(&ext_data_buffer);
+
+ return error;
+ }
return 0;
}
+/* Handles receiving a management message. Generally, this function
+ * will be called 'len' set to zero, and the length will be derived by
+ * the OpenFlow header. With the extended data message, management
+ * messages are not constrained by OpenFlow's 64K message length limit.
+ * The extended data handler calls this function with the 'len' set to
+ * the total message length and the OpenFlow header's length field is
+ * ignored.
+ */
static
-int recv_ofmp(uint32_t xid, struct ofmp_header *ofmph)
+int recv_ofmp(uint32_t xid, struct ofmp_header *ofmph, size_t len)
{
+ if (!len) {
+ len = ntohs(ofmph->header.header.length);
+ }
+
+ /* Reset the extended data buffer if this isn't a continuation of an
+ * existing extended data message. */
+ if (ext_data_xid != xid) {
+ ofpbuf_clear(&ext_data_buffer);
+ }
+
/* xxx Should sanity-check for min/max length */
switch (ntohs(ofmph->type))
{
case OFMPT_CAPABILITY_REQUEST:
- return recv_ofmp_capability_request(xid, ofmph);
+ return recv_ofmp_capability_request(xid, ofmph, len);
case OFMPT_RESOURCES_REQUEST:
- return recv_ofmp_resources_request(xid, ofmph);
+ return recv_ofmp_resources_request(xid, ofmph, len);
case OFMPT_CONFIG_REQUEST:
- return recv_ofmp_config_request(xid, ofmph);
+ return recv_ofmp_config_request(xid, ofmph, len);
case OFMPT_CONFIG_UPDATE:
- return recv_ofmp_config_update(xid, ofmph);
+ return recv_ofmp_config_update(xid, ofmph, len);
+ case OFMPT_EXTENDED_DATA:
+ return recv_ofmp_extended_data(xid, ofmph, len);
default:
VLOG_WARN_RL(&rl, "unknown mgmt message: %d",
ntohs(ofmph->type));
switch (ntohl(nh->subtype)) {
case NXT_MGMT:
- return recv_ofmp(xid, (struct ofmp_header *)oh);
+ return recv_ofmp(xid, (struct ofmp_header *)oh, 0);
default:
send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE,
- oh, htons(nh->header.length));
+ oh, ntohs(nh->header.length));
return -EINVAL;
}
}
return handler(xid, msg);
}
-void
+bool
mgmt_run(void)
{
int i;
if (!mgmt_rconn) {
- return;
+ return false;
}
+ need_reconfigure = false;
rconn_run(mgmt_rconn);
/* Do some processing, but cap it at a reasonable amount so that
VLOG_WARN_RL(&rl, "received too-short OpenFlow message");
}
}
+
+ return need_reconfigure;
}
void